JSX原理解析
1.虚拟daom
2.babel
JSX转换本质
实际上,jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。
- 所有的jsx最终都会被转换成
React.createElement的函数调用。
React.createElement在源码的什么位置呢?
createElement需要传递三个参数:
- 参数一:type
-
- 当前ReactElement的类型;
- 如果是标签元素,那么就使用字符串表示 “div”;
- 如果是组件元素,那么就直接使用组件的名称;
- 参数二:config
-
- 所有jsx中的属性都在config中以对象的属性和值的形式存储
- 参数三:children
-
- 存放在标签中的内容,以children数组的方式进行存储;
- 当然,如果是多个元素呢?React内部有对它们进行处理,处理的源码在下方
对children进行的处理:
- 从第二个参数开始,将其他所有的参数,放到props对象的children中
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
Babel官网查看
我们知道默认jsx是通过babel帮我们进行语法转换的,所以我们之前写的jsx代码都需要依赖babel。
- 可以在babel的官网中快速查看转换的过程:babeljs.io/repl/#?pres…
在这里我们编写一些jsx代码,来查看运行后的结果
<div className="app">
<div className="header">
<h1 title="标题">我是网站标题</h1>
</div>
<div className="content">
<h2>我是h2元素</h2>
<button onClick={e => console.log("+1")}>+1</button>
<button onClick={e => console.log("+1")}>-1</button>
</div>
<div className="footer">
<p>我是网站的尾部</p>
</div>
</div>
编写createElement
还有一种办法是我们自己来编写React.createElement代码
class App extends React.Component {
constructor(props) {
render() {
/*#__PURE__*/
const result = React.createElement("div", {
className: "app"
}, /*#__PURE__*/React.createElement("div", {
className: "header"
}, /*#__PURE__*/React.createElement("h1", {
title: "\u6807\u9898"
}, "\u6211\u662F\u7F51\u7AD9\u6807\u9898")), /*#__PURE__*/React.createElement("div", {
className: "content"
}, /*#__PURE__*/React.createElement("h2", null, "\u6211\u662Fh2\u5143\u7D20"), /*#__PURE__*/React.createElement("button", {
onClick: e => console.log("+1")
}, "+1"), /*#__PURE__*/React.createElement("button", {
onClick: e => console.log("+1")
}, "-1")), /*#__PURE__*/React.createElement("div", {
className: "footer"
}, /*#__PURE__*/React.createElement("p", null, "\u6211\u662F\u7F51\u7AD9\u7684\u5C3E\u90E8")));
return result;
}
}
ReactDOM.render(React.createElement(App, null) , document.getElementById("app"));
上面的整个代码,我们就没有通过jsx来书写了,界面依然是可以正常的渲染。
另外,在这样的情况下,你还需要babel相关的内容吗?不需要了
- 所以,
type="text/babel"可以被我们删除掉了; - 所以,
<script src="../react/babel.min.js"></script>可以被我们删除掉了;
虚拟DOM
虚拟DOM的创建过程
我们通过 React.createElement 最终创建出来一个 ReactElement对象:
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
这个ReactElement对象是什么作用呢?React为什么要创建它呢?
- 原因是React利用ReactElement对象组成了一个JavaScript的对象树;
- JavaScript的对象树就是大名鼎鼎的虚拟DOM(Virtual DOM);
如何查看ReactElement的树结构呢?
- 我们可以将之前的jsx返回结果进行打印;
- 注意下面代码中我打jsx的打印;
render() {
const jsx = (
<div className="app">
<div className="header">
<h1 title="标题">我是网站标题</h1>
</div>
<div className="content">
<h2>我是h2元素</h2>
<button onClick={e => console.log("+1")}>+1</button>
<button onClick={e => console.log("+1")}>-1</button>
</div>
<div className="footer">
<p>我是网站的尾部</p>
</div>
</div>
)
console.log(jsx);
return jsx;
}
打印结果,在浏览器中查看:
而ReactElement最终形成的树结构就是Virtual DOM;
为什么采用虚拟DOM
为什么要采用虚拟DOM,而不是直接修改真实的DOM呢?
- 很难跟踪状态发生的改变:原有的开发模式,我们很难跟踪到状态发生的改变,不方便针对我们应用程序进行调试;
- 操作真实DOM性能较低:传统的开发模式会进行频繁的DOM操作,而这一的做法性能非常的低;
DOM操作性能非常低:
首先,document.createElement本身创建出来的就是一个非常复杂的对象;
其次,DOM操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的DOM操作;
这里我们举一个例子:
比如我们有一组数组需要渲染:[0, 1, 2, 3, 4],我们会怎么做呢?
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
后来,我们又增加了5条数据:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for (var i=5; i<10; i++) {
var li = document.createElement("li");
li.innerHTML = arr[i];
ul.appendChild(li);
}
面这段代码的性能怎么样呢?非常低效
- 因为我们通过
document.createElement创建元素,再通过ul.appendChild(li)渲染到DOM上,进行了多次DOM操作; - 对于批量操作的,最好的办法不是一次次修改DOM,而是对批量的操作进行合并;(比如可以通过DocumentFragment进行合并);
虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
React官方的说法:Virtual DOM 是一种编程理念。
在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象,我们可以通过ReactDOM.render让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation);
这种编程的方式赋予了React声明式的API:你只需要告诉React希望让UI是什么状态,React来确保DOM和这些状态是匹配的。
你不需要直接进行DOM操作,只可以从手动更改DOM、属性操作、事件处理中解放出来;