前言
Virtual DOM
之于React
,就好比一个虚拟空间,React
的所有工作几乎都是基于Virtual DOM
完成的。其中,Virtual DOM模型
负责底层框架的构建工作,它拥有一整套的Virtual DOM
标签,并负责虚拟节点及其属性的构建、更新、删除等工作。那么Virtual DOM模型
到底是如何构建虚拟节点,如何更新节点属性的呢?
Virtual DOM的概念和诞生背景
首先看DOM
的概念,DOM
即Document Object Model(文档对象模型)
是表征页面元素的一个树形结构。
而Virtual DOM
(即虚拟DOM
),就是对真实DOM
的一个抽象,是用JavaScript
来描述的一个对象。
Virtual DOM
是随着React
的诞生而诞生的,由facebook
公司提出。它的出现,主要是为了兼顾开发效率与性能。而继React
之后,Vue 2.0
版本也引入了Virtual DOM
的概念。
Virtual DOM的优势
- 性能的保障
- 提高开发效率
- 优秀的跨平台能力
一、为什么我们不直接渲染DOM的更新呢?
真实DOM
从上图我们可以看到一个DOM
标签有非常多的属性,DOM
操作是非常昂贵的,而看似复杂的Virtual DOM
实际上效率更高。
二、DOM
标签所需的基本元素
Virtual DOM
模型中一个DOM
标签所需的基本元素标签有哪些?其实,一套简易Virtual DOM 模型
并不复杂,它只需要具备一个DOM
标签所需的基本元素即可:
- 标签名
- 节点属性(包含样式、属性、事件等)
- 子节点
- 标识
id
示例代码如下:
{
// 标签名
tagName: 'div',
// 属性
prototies: {
// 样式
style: {}
},
// 子节点
children: [],
// 唯一标识
key: 1
}
{
type: 'h1',
props: {
className: 'title',
children: 'Hello World!'
},
key: null,
ref: null
}
Virtual DOM
模型当然不止于此,却也离不开这些基础元素。现在就让我们揭下它的神秘面纱。
Virtual DOM
中的节点称为 ReactNode
,它分为3种类型 ReactElement(元素类型)
、ReactFragment
和ReactText(文本类型)
。其中,ReactElement
又分为ReactComponentElement(组件类型)
和ReactDOMElement(DOM类型)
。简单来说就是:元素类型(组件类型、DOM
类型)、Fragment
类型、文本类型。
下面是ReactNode
中不同类型节点所需要的基础元素:
type ReactNode = ReactElement | ReactFragment | ReactText;
type ReactElement = ReactComponentElement | ReactDomElement;
type ReactDOMElement = {
type: stirng,
props: {
children: ReactNodeList,
className: string,
etc,
},
key: string | boolean | number | null,
ref: string | null
};
type ReactComponentElement<TProps> = {
type: ReactClass<TProps>,
props: TProps,
key: string | boolean | number | null,
ref: string | null
};
type ReactFragment = Array<ReactNode | ReactEmpty>;
type ReactNodeList = ReactNode | ReactEmpty;
type ReactText = string | number;
type REactEmpty = nul | undefined | boolean;
那么,Virtual DOM 模型
是如何根据这些节点类型来创建元素的呢?
二、创建React
元素
下面是一段JSX
与编译后的JavaScript
:
const Nav, Profile;
// 输入(JSX);
const app = <Nav color='blue><Profile>click</Profile></Nav>;
// 输出(JavaScript)
const app = React.createElement(
Nav,
{color: 'blue'},
React.createElement(Profile, null, "click")
);
在线babel编译器
通过 JSX
创建的虚拟元素最终会被编译成调用 React
的 createElement
方法。
// createElement 只是做了简单的参数修正,返回一个 ReactElement 实例对象,
// 也就是虚拟元素的实例
ReactElement.createElement = function(type, config, children) {
// 初始化参数
var propName;
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
// 如果存在 config,则提取里面的内容
if (config != null) {
ref = config.ref === undefined ? null : config.ref;
key = config.key === undefined ? null : '' + config.key;
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// 复制 config 里的内容到 props(如 id 和 className 等)
for (propName in config) {
if (config.hasOwnProperty(propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
}
// 处理 children,全部挂载到 props 的 children 属性上。如果只有一个参数,直接赋值给 children,
// 否则做合并处理
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
// 如果某个 prop 为空且存在默认的 prop,则将默认 prop 赋给当前的 prop
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (typeof props[propName] === 'undefined') {
props[propName] = defaultProps[propName];
}
}
}
// 返回一个 ReactElement 实例对象
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};
Virtual DOM
模型通过 createElement
创建虚拟元素,那又是如何创建组件的呢?
三、初始化组件入口
当使用React
创建组件时,首先会调用instantiateReactComponent
,这是初始化组件的入口函数,它通过判断node
类型来区分不同组件的入口。
- 当
node
为空时,说明node
不存在,则初始化空组件ReactEmptyComponent.create(instantiateReactComponent)
。 - 当
node
类型为对象时,即是DOM
标签组件或自定义组件,那么如果element
类型为字
符串时,则初始化DOM
标签组件 ReactNativeComponent.createInternalComponent (element)
,否则初始化自定义组件 ReactCompositeComponentWrapper()
。
- 当
node
类型为字符串或数字时,则初始化文本组件ReactNativeComponent.createInstanceForText(node)
。 - 如果是其他情况,则不作处理。
四、文本组件
当node
类型为文本节点
时是不算Virtual DOM
元素的,但React
为了保持渲染的一致性,将其封装为文本组件ReactDOMTextComponent
。
在执行mountComponent
方法时,ReactDOMTextComponent
通过transation.useCreateElement
判断该文本是否通过createElement
方法创建的节点,如果是,则为该节点创建相应的标签和标
识 domID
,这样每个文本节点也能与其他 React
节点一样拥有自己的唯一标识,同时也拥有了
Virtual DOM diff
的权利。但如果不是通过 createElement
创建的文本,React
将不再为其创建 <span>
和 domID
标识,而是直接返回文本内容。
五、DOM 标签组件
Virtual DOM
模型涵盖了几乎所有的原生 DOM
标签,如 <div>
、<p>
、<span>
等。当开发者使用 React
时,此时的 <div>
并不是原生 <div>
标签,它其实是 React
生成的 Virtual DOM
对象,只不过标签名称相同罢了。React
的大部分工作都是在 Virtual DOM
中完成的,对于原生 DOM
而言,Virtual DOM
就如同一个隔离的沙盒
,因此 React
的处理并不是直接操作和污染原生 DOM
,这样不仅保持了性能上的高效和稳定,而且降低了直接操作原生 DOM
而导致错误的风险。
ReactDOMComponent
针对 Virtual DOM
标签的处理主要分为以下两个部分:
- 属性的更新,包括更新样式、更新属性、处理事件等;
- 子节点的更新,包括更新内容、更新子节点,此部分涉及
diff
算法(diff
算法请看参考文档React列表中的key属性)。
1. 更新属性
当执行 mountComponent
方法时,ReactDOMComponent
首先会生成标记和标签,通过 this.createOpenTagMarkupAndPutListeners(transaction)
来处理 DOM
节点的属性和事件。
- 如果存在事件,则针对当前的节点添加事件代理,即调用 `enqueuePutListener(this,
propKey, propValue, transaction)`。
- 如果存在样式,首先会对样式进行合并操作
Object.assign({}, props.style)
,然后通过
CSSPropertyOperations.createMarkupForStyles(propValue, this)
创建样式。
- 通过
DOMPropertyOperations.createMarkupForProperty(propKey, propValue)
创建属性。 - 通过
DOMPropertyOperations.createMarkupForID(this._domID)
创建唯一标识。
2. 更新子节点
当执行 mountComponent
方法时,ReactDOMComponent
会通过this._createContentMarkup(transaction, props, context)
来处理 DOM
节点的内容。
当执行 receiveComponent
方法时,ReactDOMComponent
会通过 this._updateDOMChildren(lastProps, nextProps, transaction, context)
来更新 DOM
内容和子节点。
先是删除不需要的子节点和内容。如果旧节点存在,而新节点不存在,说明当前节点在更新
后被删除,此时执行方法 this.updateChildren(null, transaction, context)
;如果旧的内容存在,而新的内容不存在,说明当前内容在更新后被删除,此时执行方法this.updateTextContent('')
。
再是更新子节点和内容。如果新子节点存在,则更新其子节点,此时执行方法 this.updateChildren(nextChildren, transaction, context)
;如果新的内容存在,则更新内容,此时执行方法 this.updateTextContent('' + nextContent)
。
六、自定义组件
ReactCompositeComponent 自定义组件
实现了一整套 React
生命周期和 setState
机制,因此自定义组件是在生命周期的环境中进行更新属性、内容和子节点的操作。这些更新操作与ReactDOMComponent
的操作类似。
七、总结
React
在16
版本之后,对Virtual DOM
进行了更新,但主要思想还是一致的,想要了解的可参考React Fiber原理解析这篇文章。
Virtual DOM
和真实DOM
的对比和适用场景
首次渲染速度表现 | 更新数据时速度表现 | 适用场景 | Virtual DOM | 一般 | 快 | 几乎都适用,在首次渲染和一些特殊场景下会稍慢,但是可以接受。 | 真实DOM | 快 | 一般 | 首次渲染和少量数据操作表现较好,数据量较大时更新操作较慢。 |
---|
Virtual DOM
的性能优势并不完全的体现在“快”,而是体现在它还拥有着更出色的综合性能。
参考文档:
React列表中的key属性
React Fiber原理解析
Virtual DOM 及内核
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!