问题现象
以下是这个用例的内容:
1 | it('uses the fallback value when in an environment without Symbol', () => { |
他测试的是在不支持Symbol的环境,jsx
的内部属性$$typeof
是否正确。
我们知道,jsx
仅仅是JS
的语法糖,在编译时会被编译成函数调用,比如:
1 | // 编译前 |
在React.createElement
(或jsxRuntime.jsx
)方法的实现中,最终会返回如下数据结构:
1 | const element: ReactElement = { |
其中$$typeof
属性用于区分jsx对象的类型,比如REACT_ELEMENT_TYPE
代表这个jsx对象
是一个React Element
。
在支持Symbol
的环境,$$typeof
对应一个唯一的symbol
。在不支持的环境,对应一个16进制数字。
比如REACT_ELEMENT_TYPE
的定义如下:
1 | const supportSymbol = typeof Symbol === 'function' && Symbol.for; |
回到我们的测试用例,他的测试意图就很明显了:在不支持Symbol
的环境,div对应jsx对象的$$typeof
属性应该返回数字0xeac7
。
1 | it('uses the fallback value when in an environment without Symbol', () => { |
那么如何制造一个不支持Symbol的环境呢?
很简单,在所有用例执行前的beforeEach
钩子函数(jest
提供的)中将global.Symbol
置为undefined
:
1 | beforeEach(() => { |
当引入react
、react-dom
时,其内部执行时global.Symbol === undefined
。
这就模拟了不支持Symbol的环境。
但是这个用例却挂了:
[图片上传失败…(image-d22e3d-1657982127417)]
上述代码应该是没问题的,毕竟是React
官方会跑的用例。那么问题出在哪儿呢?
babel的锅
在React17
发布时,带来了全新的 JSX 转换。
在17之前,jsx
会编译为React.createElement
,17之后会编译为jsxRuntime.jsx
。
同时会在模块顶部引入如下语句:
1 | import { jsx as _jsx } from "react/jsx-runtime"; |
上述被引入的语句的执行先于下述语句:
1 | originalSymbol = global.Symbol; |
所以在语句执行时,环境中还存在global.Symbol
,就造成开篇提到的问题。
那为什么React
官方跑用例时没有问题呢?
答案是:React
跑用例时会将jsx
编译为React.createElement
。
这样不会在模块顶部插入新的引入语句。
当引入React
时,环境中已经不存在global.Symbol
了:
1 | originalSymbol = global.Symbol; |
总结
由于编译在内存中进行,不太好排查编译后代码。所以如果对React
各方面特性了解不深的话,这个问题真不太好排查。
当前big-react代码量还比较少,对从0实现React
感兴趣的朋友可以关注下,给个star
哦~