说起JSX,几乎无人不知:它在React扮演着描述了UI界面、关联渲染逻辑的重要角色。但JSX如何被编译、又如何变成一个个的元素,相信就没那么多人知道了。本文将对JSX编译结果分析,以加深对React里JSX工作原理的理解
准备JSX编译环境
需要安装三个包(开发依赖)
- @babel/cli
- @babel/core
- @babel/preset-react
安装完毕在根目录添加配置文件.babelrc
,内容如下:
{
"presets": ["@babel/preset-react"]
}
复制代码
最后在终端里输入命令:
npx babel .\index.jsx -w -o .\index.js
将index.jsx
编译到index.js
,并开启修改监听模式
基本示例
const h1 = <h1>this is a H1 Tag</h1>
复制代码
经过babel编译:
const h1 = React.createElement("h1", null, "this is a H1 Tag");
复制代码
可以看到babel会对JSX
进行解析,而后将DOM节点相关的数据片作为参数传递给React.createElement方法调用,这个方法是干嘛的呢?
React.createElement被链接到一个名为createElementWithValidation
的函数,它有三类参数:
- type:表示节点的类型
- props:表示节点的属性
- children-表示节点的子元素(可能有多个)
function createElementWithValidation(type, props, children) {
//...
var element = createElement.apply(this, arguments);
//...
return element;
}
复制代码
这样编译结果就很好理解了:
h1
是节点的类型(type),属性为空(null),有一个子元素且为文本节点(children):”this is a H1 Tag”。
所以JSX最后会成为一个element(react元素),本质上是一个javascript对象。
JSX各种嵌入场景
我们可以将普通表达式、jsx本身作为整体被传入React.createElement
的children
或者props
属性
- 表达式作为属性值
var className = "div_class"
const div = <div class={'prefix_'+className}></div>
复制代码
编译为:
var className = "div_class";
const div = React.createElement("div", {
class: 'prefix_' + className
});
复制代码
- JSX嵌套JSX(作为另一个JSX的子元素)
const childJsx = <span>child span</span>
const parentJsx = <div>{childJsx}</div>
复制代码
编译为:
const childJsx = React.createElement("span", null, "child span");
const parentJsx = React.createElement("div", null, childJsx);
复制代码
- JSX最外层只能包含一个节点
//以下会报错,JSX expressions must have one parent element
const multipleWrapper = <p>first p</p><p>second p</p>
复制代码
每个JSX都代表了一个树节点,这个节点可以作为一个其他节点的载体又可以挂载到其他树节点上,树节点有且只能有一个root。
- 搭配逻辑运算符
let condition = true;
const jsxExamp = <div>{condition &&[1,2,3].join("-") }</div>
复制代码
编译为:
let condition = true;
const jsxExamp = React.createElement("div", null, condition && [1, 2, 3].join("-"));
复制代码
函数、class组件的编译
以上提到的都是JSX字面量的编译,JSX的编译不限于此,也可能发生在Class组件、函数组件的内部如:
function FnComp(){
return <div>A Function Component</div>
}
class ClassComp extends React.Component{
render(){
return <div>
<h1>A Class Component</h1>
<FnComp/>
</div>
}
}
复制代码
被编译为:
function FnComp() {
return React.createElement("div", null, "A Function Component");
}
class ClassComp extends React.Component {
render() {
return React.createElement("div", null, React.createElement("h1", null, "A Class Component"), React.createElement(FnComp, null));
}
}
复制代码
最后
当然问题不止于此,以后开发再也不怕遇到JSX编译的错误了,因为我们可以借助Babel工具查看编译结果并分析查找出错原因咯。
再总结一下:
JSX会先经过babel的预编译和解析,然后将结果通过React.createElement
完成替换,而React.createElement
则创建成一个个的reactElement
,而这些reactElement
本质上是JavaScript对象,包含了节点的所有信息,后续会经过一系列的加工和调度渲染到网页上。