文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

实现React过程中一次有趣的问题排查经历

2024-12-01 19:26

关注

大家好,我卡颂。

逞着对React内部运行流程还记得住,业余时间尝试复刻一个React —— big-react[1]。

即然是复刻一个React,那肯定得跑通部分官方的测试用例。

在跑一个用例时遇到个很有意思的问题,以下是排查过程。

问题现象

以下是这个用例的内容:

it('uses the fallback value when in an environment without Symbol', () => {
expect((<div />).$$typeof).toBe(0xeac7);
});

他测试的是在「不支持Symbol的环境」,jsx的内部属性$$typeof是否正确。

我们知道,jsx仅仅是JS的语法糖,在编译时会被编译成函数调用,比如:

// 编译前
<div />
// 编译后 React17之前
React.createElement('div');
// 编译后 React17之后
jsxRuntime.jsx('div');

在React.createElement(或jsxRuntime.jsx)方法的实现中,最终会返回如下数据结构:

const element: ReactElement = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref,
props
};

其中$$typeof属性用于区分「jsx对象的类型」,比如REACT_ELEMENT_TYPE代表这个jsx对象是一个React Element。

在支持Symbol的环境,$$typeof对应一个唯一的symbol。在不支持的环境,对应一个16进制数字。

比如REACT_ELEMENT_TYPE的定义如下:

const supportSymbol = typeof Symbol === 'function' && Symbol.for;
export const REACT_ELEMENT_TYPE = supportSymbol
? Symbol.for('react.element')
: 0xeac7;

回到我们的测试用例,他的测试意图就很明显了:在不支持Symbol的环境,「div对应jsx对象」的$$typeof属性应该返回数字0xeac7。

it('uses the fallback value when in an environment without Symbol', () => {
expect((<div />).$$typeof).toBe(0xeac7);
});

那么如何制造一个「不支持Symbol的环境」呢?

很简单,在所有用例执行前的beforeEach钩子函数(jest提供的)中将global.Symbol置为undefined:

beforeEach(() => {
jest.resetModules();
originalSymbol = global.Symbol;
// 制造不支持Symbol的环境
global.Symbol = undefined;
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
});

当引入react、react-dom时,其内部执行时global.Symbol === undefined。

这就模拟了「不支持Symbol的环境」。

但是这个用例却挂了:

上述代码应该是没问题的,毕竟是React官方会跑的用例。那么问题出在哪儿呢?

babel的锅

在React17发布时,带来了全新的 JSX 转换[2]。

在17之前,jsx会编译为React.createElement,17之后会编译为jsxRuntime.jsx。

同时会在模块顶部引入如下语句:

import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";

上述被引入的语句的执行先于下述语句:

originalSymbol = global.Symbol;
global.Symbol = undefined;

所以在语句执行时,环境中还存在global.Symbol,就造成开篇提到的问题。

那为什么React官方跑用例时没有问题呢?

答案是:React跑用例时会将jsx编译为React.createElement。

这样不会在模块顶部插入新的引入语句。

当引入React时,环境中已经不存在global.Symbol了:

originalSymbol = global.Symbol;
global.Symbol = undefined;
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');

总结

由于编译在内存中进行,不太好排查编译后代码。所以如果对React各方面特性了解不深的话,这个问题真不太好排查。

当前big-react[3]代码量还比较少。

参考资料

[1]big-react:https://github.com/BetaSu/big-react。

[2]全新的 JSX 转换:https://zh-hans.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html。

[3]big-react:https://github.com/BetaSu/big-react。

来源:魔术师卡颂内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯