在破坏React元素创建过程的同时,从内部来看
这篇文章是Bitkey公司2023年圣诞节日历的第13篇文章。
首先
你好!我是川又,在Bitkey株式会社担任SET。这是我第一次在Qiita上写作!在Bitkey,我们在服务开发中使用React。
我也是,目前在使用React,但是如果有没有碰过或者没有看过React代码库的人,请务必借此机会一起进行实际操作来了解其内部实现。
首先,将React的代码库克隆到本地。
git clone https://github.com/facebook/react.git
yarn install
git checkout main #最新バージョンのReactに向けます
为了确认内部处理,请编写测试。
我打算在react-dom下进行编写。
# root配下にて
touch packages/react-dom/src/__tests__/ReactHoge-test.js
样本测试实施
'use strict';
let React;
let ReactDOM;
describe('reactdom', () => {
beforeEach(() => {
React = require('react');
ReactDOM = require('react-dom');
});
it('JSXをHTMLに変換する', () => {
// Arrange
function App() {
return (
<div>
<div>
<div>
<span>hello</span>
</div>
</div>
</div>
);
}
// Act
const container = document.createElement('div');
console.log(container.innerHTML)
ReactDOM.render(<App />, container);
// Assert
expect(container.innerHTML).toBe(
'<div><div><div><span>hello</span></div></div></div>',
);
});
});
进行测试
# root配下にて
yarn test packages/react-dom/src/__tests__/ReactHoge-test.js
结果
console.log
<div><div><div><span>hello</span></div></div></div>
at Object.<anonymous> (packages/react-dom/src/__tests__/ReactHoge-test.js:26:13)
PASS packages/react-dom/src/__tests__/ReactHoge-test.js
reactdom
✓ JSXがHTMLに代わる (394 ms)
没有问题地将定义好的组件添加到DOM中。
显然,DOM的div嵌套很多,HTML不易于阅读。为了改善外观,让我们改变React的行为,将
去掉。
破坏React元素创建处理
在实现React时,使用JSX是基本的,但是需要注意的是,JSX只是JavaScript语法的扩展,会被转换为其他形式来进行处理。
为了知道在代码库中元素被创建的位置,需要深入理解JSX。
查看公式文件后发现有相关文章。
在React中,似乎是通过createElement创建React元素的。
在代码库中进行调查后,我们发现以下内容在packages/react/src/React.js中定义。
const createElement: any = __DEV__
? createElementWithValidation
: createElementProd;
在实施过程中,我发现有两种情况,一种是运行验证,另一种是不运行验证。从性能方面考虑,在生产环境中不需要运行各种验证。因此,让我们确认一下不带验证的createElement。
让我们来确认一下定义的先验条件。
// packages/react/src/ReactElement.js
export function createElement(type, config, children) {
let propName;
// Reserved names are extracted
const props = {};
...
createElement函数可以接受三个参数,分别是type、config和children。
在测试中定义的组件会被转译成以下的Javascript。
React.createElement('div', null,
React.createElement('div', null,
React.createElement('div', null,
React.createElement('span', null, 'hello')
)
)
);
在Chinese,我们甚至可以传递React组件并不限于HTML标签的类型。
config是一个对象(props的信息)或者为null。由于此次没有传递任何内容,所以会变成null。
children可以传递给React的节点的全部内容。本例中是由createElement输出的React元素。
和DOM一样,调用也发生了嵌套?
我将更改createElement的行为,不再渲染div。
根据type的定义,似乎确定了要插入到DOM中的元素。
如果传递了type=’div’,我们可以返回另一个元素。
// packages/react/src/ReactElement.js
export function createElement(type, config, children) {
if (type === 'div') {
type = 'hoge';
} // if文を追加
let propName;
...
我将再次进行测试。
我现在开始发出以下警告
Warning: The tag <hoge> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.
at hoge
at hoge
at hoge
at App
在HTML中并不存在这个标签,所以产生警告是理所当然的。
不过有趣的是,
● reactdom › JSXがHTMLに代わる
expect(received).toBe(expected) // Object.is equality
Expected: "<div><div><div><span>hello</span></div></div></div>"
Received: "<hoge><hoge><hoge><span>hello</span></hoge></hoge></hoge>"
我们成功去除了div标签,但是需要确保它能正常运行。
无需任何内容即可正常呈现
让我们添加一场测试吧。
目标就只剩下hello了,对吧。
it('divを無くせる', () => {
// Arrange
function App() {
return (
<div>
<div>
<div>
<span>hello</span>
</div>
</div>
</div>
);
}
// Act
const container = document.createElement('div');
ReactDOM.render(<App />, container);
console.log(container.innerHTML)
//Assert
expect(container.innerHTML).toBe('<span>hello</span>');
});
可以通过将常见的片段替换来实现不使用div进行正常渲染。
如果按照以下方式定义,由于不存在与”hohe”相同的”fragment”标签,因此可能会受到批评。
// packages/react/src/ReactElement.js
export function createElement(type, config, children) {
if (type === 'div') {
type = 'fragment';
}
let propName;
...
// Warning: The tag <fragment> is unrecognized in this browser
通常在JSX中,我们使用 <> </> 的形式来编写Fragment。但是如果要在内部进行替换,应该传递转译后的类型。让我们在代码库内引用Fragment。
让我们以Babel提供的编译器演示来查看JSX的React片段会以哪种形式编译。
经过编译,简单的 JSX 已被转换。由于 Fragment 被调用到 createElement 中,我们需要查看导入的 react/jsx-runtime 的内容。
包/packages/react/jsx-runtime.js
export {Fragment, jsx, jsxs} from './src/jsx/ReactJSX';
我试图跟着 Fragment 的定义跳转,最终到达了 ReactSymbols.js。
看起来,所有 React 元素的类型都在这里定义?。
让我们将相应的类型定义定义在createElement函数中。
import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
export function createElement(type, config, children) {
if (type === 'div') {
type = REACT_FRAGMENT_TYPE;
}
let propName;
...
进行测试
console.log
<span>hello</span>
at Object.<anonymous> (packages/react-dom/src/__tests__/ReactHoge-test.js:45:13)
PASS packages/react-dom/src/__tests__/ReactHoge-test.js
reactdom
✓ divを無くせる (134 ms)
渲染时成功地删除了div的所有内容?
結局
這次我們使用實作式的方式來看React元素的建立過程。
createElement只是React UI架構中的一個階段而已。還有後續的處理,像是調和(React Fiber)、掛載DOM、狀態和生命周期等話題。我們可以在相應的函數中加入console log,或改變行為,同時玩著React的內部實現,一起學習吧!
2023 年的第 14 天,株式会社 BitKey 的 Advent Calendar 将由 @takumi_sakao 负责!敬请期待✨