大家好,我卡颂。
最近对于React
的新书交稿了(预计年底出版),工夫比拟多。
趁着对React
外部运行流程还记得住,业余时间尝试复刻一个React
—— big-react。
即然是复刻一个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 转换。
在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代码量还比拟少,对从0实现React
感兴趣的敌人能够关注下,给个star
哦~