实现React过程中一次有趣的问题排查经历
大家好,我卡颂。
最近关于React
的新书交稿了(预计年底出版),时间比较多。
【实现React过程中一次有趣的问题排查经历】趁着对React
内部运行流程还记得住,业余时间尝试复刻一个React
—— big-react。
即然是复刻一个React
,那肯定得跑通部分官方的测试用例。
在跑一个用例时遇到个很有意思的问题,以下是排查过程。
欢迎加入人类高质量前端框架群,带飞
问题现象
以下是这个用例的内容:
it('uses the fallback value when in an environment without Symbol', () => {
expect(().$$typeof).toBe(0xeac7);
});
他测试的是在不支持Symbol的环境,
jsx
的内部属性$$typeof
是否正确。我们知道,
jsx
仅仅是JS
的语法糖,在编译时会被编译成函数调用,比如:// 编译前// 编译后 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(().$$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
哦~推荐阅读
- Java 中的对象池实现
- 使用 LSM Tree 思想实现一个 KV 数据库
- 资源协作服务上线,帮你实现更灵活的权限配置与资源共享
- .NET|.NET Core 实现后台任务(定时任务)BackgroundService(二)
- [Qt学习笔记]QLabel实现圆形警示灯显示方法
- MySQL|MySQL 读写分离的基本概念和实现方式
- SSM个人博客系统的设计与实现
- java|java IP归属地功能实现详解
- 使用js编写实现拼图游戏
- vue.js|毕业实习:Vue+Springboot+MySQL实现的订餐系统