const toLearn = ['react', 'vue', 'webpack', 'nodejs'];
const TextComponent = () => <div> hello , i am function component </div>;
class Index extends React.Component {
status = false;
renderFoot = () => <div> i am foot</div>;
render() {
return <div style={{ marginTop: '100px' }} className="container">
<div>hello,world</div>
<React.Fragment>
<div> 👽👽 </div>
</React.Fragment>
my name is alien
{toLearn.map(item => <div key={item}>let us learn {item}</div>)}
<TextComponent />
{ this.status ? <TextComponent /> : <div>三元运算</div> }
{ this.renderFoot() }
<button onClick={() => console.log(this.render())}>打印 render 后的内容</button>
</div>
};
}
JSX 元素节点会被编译成 React Element 形式:
React.createElement(
type,
[props],
[...children]
)
createElement
的参数:
div
或 span
等字符串props
attributes
标签属性<div>
<TextComponent />
<div>hello, world</div>
let us learn React!
</div>
babel 编译后:
/*#__PURE__*/ React.createElement(
"div",
null,
/*#__PURE__*/ React.createElement(TextComponent, null),
/*#__PURE__*/ React.createElement("div", null, "hello, world"),
"let us learn React!"
);
jsx 转换规则:
jsx 元素类型 | react.createElement 转换后 | type 属性 |
---|---|---|
element 元素类型 | react element 类型 | 标签字符串,如 div |
fragment 类型 | react element 类型 | symbol react.fragment 类型 |
文本类型 | 字符串 | 无 |
数组类型 | 返回数组结构,里面的元素被 react.createElement 转换 | 无 |
组件类型 | react element 类型 | 组件类或组件函数本身 |
三元运算 / 表达式 | 先执行三元运算,然后按上面规则转换 | 看三元运算结果的类型 |
函数执行 | 先执行函数,然后按上面规则转换 | 看函数执行结果的类型 |
最后,在调和阶段,上述 React element 对象的每一个子节点都会形成一个对应的 fiber 对象,然后通过 sibling
、return
、child
将每一个 fiber 对象联系起来。
React 针对不同 React element 对象会产生不同 tag (种类) 的 fiber 对象:
export const FunctionComponent = 0; // 函数组件
export const ClassComponent = 1; // 类组件
export const IndeterminateComponent = 2; // 初始化的时候不知道是函数组件还是类组件
export const HostRoot = 3; // Root Fiber 可以理解为根元素 , 通过 reactDom.render() 产生的根元素
export const HostPortal = 4; // 对应 ReactDOM.createPortal 产生的 Portal
export const HostComponent = 5; // dom 元素 比如 <div>
export const HostText = 6; // 文本节点
export const Fragment = 7; // 对应 <React.Fragment>
export const Mode = 8; // 对应 <React.StrictMode>
export const ContextConsumer = 9; // 对应 <Context.Consumer>
export const ContextProvider = 10; // 对应 <Context.Provider>
export const ForwardRef = 11; // 对应 React.ForwardRef
export const Profiler = 12; // 对应 <Profiler/ >
export const SuspenseComponent = 13; // 对应 <Suspense>
export const MemoComponent = 14; // 对应 React.memo 返回的组件
jsx 最终形成的 fiber 结构图:
fiber 对应关系:
注意,JSX 中 map 数组结构的子节点,外层会被加上 fragment,map 返回数组结构作为 fragment 的子节点。
对 render 过程进行介入:
class Index extends React.Component {
status = false;
renderFoot = () => <div> i am foot</div>;
controlRender = () => {
const reactElement = (
<div style={{ marginTop: '100px' }} className="container">
<div>hello,world</div>
<React.Fragment>
<div> 👽👽 </div>
</React.Fragment>
my name is alien
{toLearn.map(item => <div key={item}>let us learn {item}</div>)}
<TextComponent />
{ this.status ? <TextComponent /> : <div>三元运算</div> }
{ this.renderFoot() }
<button onClick={() => console.log(this.render())}>打印 render 后的内容</button>
</div>
);
console.log(reactElement);
/** 扁平化 children */
const { children } = reactElement.props;
const flatChildren = React.Children.toArray(children);
console.log(flatChildren);
/** 除去文本节点 */
const newChildren: any = [];
React.Children.forEach(flatChildren, (item) => {
if (React.isValidElement(item)) {
newChildren.push(item);
}
});
/** 插入新节点 */
const lastChildren = React.createElement(
'div',
{
className: 'last'
},
'say goodbye'
);
newChildren.push(lastChildren);
/** 修改容器节点 */
const newReactElement = React.cloneElement(
reactElement,
{},
...newChildren
);
}
render() {
return this.controlRender();
}
}
Q: React.createElement
和 React.cloneElement
的区别?
A: React.createElement
用于创建一个新的 React element 对象,React.cloneElement
用于修改一个已有的 React element 对象,返回一个新的 React element 对象。
@babel/plugin-syntax-jsx
插件可以让 Babel 有效解析 JSX 语法@babel/plugin-transform-react-jsx
内部使用 @babel/plugin-syntax-jsx
插件,可以将 React JSX 转换为 JS 能识别的 createElement
格式新版本 React 已经不需要引入 createElement
,这种模式来源于 Automatic Runtime
:
function Index() {
return <div>
<h1>hello,world</h1>
<span>let us learn React</span>
</div>
}
编译后:
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
function Index() {
return /*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("h1", {
children: "hello,world"
}),
/*#__PURE__*/ _jsx("span", {
children: "let us learn React"
})
]
});
}
需要在 .babelrc
设置 runtime: automatic
:
{
"presets": [
[
"@babel/preset-react",
{
"runtime": "automatic"
}
]
]
}
在经典模式下,使用 JSX 的文件需要引入 React:
import React from 'react';
function Index() {
return <div>
<h1>hello,world</h1>
<span>let us learn React</span>
</div>
}
编译后:
import React from "react";
function Index() {
return /*#__PURE__*/ React.createElement(
"div",
null,
/*#__PURE__*/ React.createElement("h1", null, "hello,world"),
/*#__PURE__*/ React.createElement("span", null, "let us learn React")
);
}
模拟 Babel 处理 JSX:
element.jsx
测试代码:import React from 'react';
function TestComponent() {
return <p>hello, React</p>
}
function Index() {
return <div>
<span>模拟 babel 处理 jsx 流程</span>
<TestComponent />
</div>
}
export default Index;
jsx.js
模拟编译效果:const fs = require('fs');
const babel = require('@babel/core');
const code = fs.readFileSync('./element.jsx', 'utf-8');
const result = babel.transformSync(code, {
plugins: [
'@babel/plugin-transform-react-jsx'
]
});
fs.writeFileSync('./element.js', result.code);
element.js
编译后:import React from 'react';
function TestComponent() {
return /*#__PURE__*/React.createElement("p", null, "hello, React");
}
function Index() {
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("span", null, "\u6A21\u62DF babel \u5904\u7406 jsx \u6D41\u7A0B"), /*#__PURE__*/React.createElement(TestComponent, null));
}
export default Index;