头图

Hello everyone, I'm Casson.

Recently, a new book about React has been handed in (expected to be published by the end of the year), and there is a lot of time.

While I still remember the internal running process of React , try to copy a React big-react in my spare time.

Since it is a replica React , it must run through some official test cases.

I encountered a very interesting problem when running a use case. The following is the troubleshooting process.

Welcome to join the human high-quality front-end framework group , with flying

problem phenomenon

Here's what this use case looks like:

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

He tested whether the internal attribute ---ca25edf5855134665c947f430fe25524 jsx of $$typeof is correct in an environment that does not support Symbol .

We know that jsx is just the syntactic sugar of JS , which will be compiled into function calls at compile time, for example:

 // 编译前
<div />

// 编译后 React17之前
React.createElement('div');

// 编译后 React17之后
jsxRuntime.jsx('div');

In the implementation of the React.createElement (or jsxRuntime.jsx ) method, the following data structure is finally returned:

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

The $$typeof attribute is used to distinguish the type of jsx object , such as REACT_ELEMENT_TYPE represents this jsx对象 is a React Element .

In environments that support Symbol , $$typeof corresponds to a unique symbol . In unsupported environments, corresponds to a hexadecimal number.

For example REACT_ELEMENT_TYPE is defined as follows:

 const supportSymbol = typeof Symbol === 'function' && Symbol.for;

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

Back to our test case, his test intention is obvious: in an environment that does not support Symbol , the div corresponding to the $$typeof attribute of the jsx object should return a number 0xeac7 .

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

So how do you make an environment that doesn't support Symbols ?

很简单,在所有用例执行前的beforeEach函数( jest的)中将global.Symbolundefined

 beforeEach(() => {
        jest.resetModules();

        originalSymbol = global.Symbol;
    // 制造不支持Symbol的环境
        global.Symbol = undefined;

        React = require('react');
        ReactDOM = require('react-dom');
        ReactTestUtils = require('react-dom/test-utils');
    });

When react and react-dom are introduced, the internal execution is global.Symbol === undefined .

This simulates an environment that does not support Symbols .

But this use case hangs:

The above code should be no problem, after all, it is the use React that the official will run. So where is the problem?

babel's pot

When React17 was released, it brought a brand new JSX transformation .

Before 17, jsx will be compiled as React.createElement , after 17 will be compiled as jsxRuntime.jsx .

At the same time, the following statement will be introduced at the top of the module:

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

Execution of the above-introduced statement precedes the following statement:

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

Therefore, when the statement is executed, there is still global.Symbol in the environment, which causes the problem mentioned in the opening paragraph.

Then why React there is no problem when running the official use case?

The answer is: React will compile ---d0221215bcd320717a60e9bd46fd7339 jsx to React.createElement when running the use case.

This will not insert a new import statement at the top of the module.

When ---3cc600023e84f3e0e38907a17a0b2bde--- is introduced, React global.Symbol longer exists in the environment:

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

React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');

Summarize

Since the compilation is done in memory, it is not easy to troubleshoot the compiled code. So if you don't have a deep understanding of the characteristics of React , this problem is really not easy to troubleshoot.

At present, the amount of big-react code is still relatively small. For those who are interested in implementing it from 0 React , you can pay attention and give a star oh~


卡颂
3.1k 声望16.7k 粉丝