关于前端:实现React过程中一次有趣的问题排查经历

6次阅读

共计 2111 个字符,预计需要花费 6 分钟才能阅读完成。

大家好,我卡颂。

最近对于 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');
    });

当引入 reactreact-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 哦~

正文完
 0