在讲解虚拟DOM(Virtual DOM)以前能够先了解下真实的DOM是什么html
Virtual DOM 本质上是JavaScript对象,是对真实DOM的的一种描述方式。
即JS对象模拟的DOM结构,将DOM变化的对比放在JS层来作。让UI渲染变成 UI = f(data)的形式。
示例:HTML里的一段标签文本前端
<ul id="list">
<li class="item">Item1</li>
<li class="item">Item2</li>
</ul>
复制代码
用虚拟DOM结构能够表示为:react
{
tag: "ul",
attrs: {
id: "list"
},
children: [
{
tag: "li",
attrs: { className: "item" },
children: ["Item1"]
}, {
tag: "li",
attrs: { className: "item" },
children: ["Item2"]
}
]
}
复制代码
tag是标签名称
attrs是标签属性组成的对象
children是包含在标签内的子标签git
因此,Virtual DOM 实际上是一个JS对象,相似JSON格式,React中就是将JSX元素的名称、属性和内容做为对象及其属性来建立Virtual DOM。github
不过,针对单一场景的性能优化,专门针对该场景的js进行极致优化 比 Virtual DOM 方案更适用。算法
在React开发中,Virtual DOM 元素便是React 元素,常常会使用JSX来写React元素,好比
简单的api
<div>Hello</div>
复制代码
稍微复杂一些的浏览器
<div className="divStyleName" onClick={(e) => console.log('点击了')}>
Hello
<img src="pic_url">img</img>
<div>div2</div>
<div></div>
</div>;
复制代码
不过,在React中不是必需要使用JSX的。
由于其实 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖。其本质仍是JavaScript,全部的JSX语法最终都会被转换成对这个方法的调用。所以,使用 JSX 能够完成的任何事情均可以经过纯 JavaScript 完成。
同时,浏览器是不能直接读取JSX,为了使浏览器可以读取JSX,须要在项目构建环境中配置有关 JSX 编译。好比 让 Babel 这样的 JSX 转换器将 JSX语句 转换为 目标 js 代码,再将其传给浏览器,JS引擎才能成功执行语句。性能优化
用纯JavaScript来写上面的JSX示例(通过babel转译)服务器
React.createElement("div", {
className: "divStyleName",
onClick: e => console.log('点击了')
}, "Hello", React.createElement("img", {
src: "pic_url"
}, "img"), React.createElement("div", null, "div2"), React.createElement("div", null));
复制代码
从最层的React.createElement分析起
第一个参数值是组件名
第二个参数值是一个对象,由组件的属性构成
第三到后面的一系列参数值,就是子元素了。若是子元素依然是JSX元素,就继续经过React.createElement来建立对象
React.createElement建立的React元素就是虚拟DOM树的元素,因而,就构成出一个虚拟DOM树了。
这里在浏览器窗口中看下上面所述内容的结果
再来自定义一个组件,名为HelloComponent,JSX实现为
class HelloComponent extends React.Component {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
<HelloComponent toWhat="World" />
复制代码
Babel转译后对应的js代码为
class HelloComponent extends React.Component {
render() {
return React.createElement("div", null, "Hello ", this.props.toWhat);
}
}
React.createElement(HelloComponent, {
toWhat: "World"
});
复制代码
而后在浏览器窗口看下
注意这里type的值是一个类构造函数了
这是babel在编译时会判断JSX中组件的首字母:
因此自定义组件名称必需要首字母大写。
下面就来研究下React.createElement(component, props, ...children)具体的实现 以及 是如何来构建Virtual DOM的吧~
要开始看React 源码啦~
由于是React.createElement,因此天然就找到React.js文件,查看其createElement方法。 而后就发现其实的调用了ReactElement.js文件中的createElement方法,返回一个ReactElement对象
/** * Create and return a new ReactElement of the given type. * See https://reactjs.org/docs/react-api.html#createelement */
export function createElement(type, config, children) {
let propName;
// Reserved names are extracted
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
/* ... 根据入参各类逻辑判断赋值ref、source、self、props等 ... */
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
复制代码
再看下ReactElement方法的具体实现
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
if (__DEV__) {
// 天然是给element的各个属性赋值了
}
return element;
};
复制代码
返回的是是一个JS对象,在React中称为ReactElement对象:
{
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
}
复制代码
结构正是上面在浏览器窗口中看到的React.createElement的结果的结构。
这里简单介绍下ReactElement对象的属性及其含义。
了解了单个ReactElement对象后,前面提到的示例层层调用React.createElement,就能够建立出一个Virtual DOM Tree了,即生成原生的虚拟 DOM 对象。
React.createElement("div", {
className: "divStyleName",
onClick: e => console.log('点击了')
}, "Hello", React.createElement("img", {
src: "pic_url"
}, "img"), React.createElement("div", null, "div2"), React.createElement("div", null));
复制代码
在线Babel编辑器编写查看更多JSX转换成JavaScript的示例