React Virtual DOM
使用过react开发的人员一定听过react的Virtual DOM,那Virtual DOM到底是什么呢?接下我们来一探究竟。
React 基于16.13.0版本讲解
首先我们先来打印下React是什么
我们再来看一下源码的定义
var React = {
// 子元素
Children: {
map: mapChildren, // 遍历子元素
forEach: forEachChildren, // 类似map,但没有返回
count: countChildren, // 返回 children 当中的组件总数
toArray: toArray, // 将Children转换为数组
only: onlyChild, 返回 children 中 仅有的子级。否则抛出异常。
},
Component, // 用来创建 React 组件类。
Fragment, // 空标签,可带有key属性
Profiler, // 测量渲染一个 React 应用多久渲染一次以及渲染一次的“代价”(生产环境中禁用)。
PureComponent, // 用来创建 React 纯组件类。
StrictMode, // 严格模式
Suspense, // 可以使包裹的组件做优雅降级(如 loading 指示器等)。
cloneElement, // 拷贝 React 元素。
createContext, // 创建Context。
createElement, // 创建 React 元素。
createFactory, // 创建 React 工厂函数。(不建议使用)。
createRef, // 创建ref
forwardRef, // 转发ref
isValidElement, // 判断是否是有效的 React 元素。
lazy, // 懒加载,配合webpack使用会让你爱上它。
memo, // React.PureComponent类似,用于一个函数组件而非一个类组件。
// 以下都是16.8以后的新特性Hooks
useCallback,
useContext,
useDebugValue,
useEffect,
useImperativeHandle,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
version,
};
从控制台和源码都可以看到React是一个对象。
我们再来打印下<h1>hello world!
我们再打印出<App />,App的结构如下:
<div>
<h1>App</h1>
<p>Hello world!</p>
</div>
可以很直观的发现,打印的 HTML 元素并不是真实的 DOM 元素,打印的组件也不是 DOM 元素的集合,所有打印出来的元素都是一个对象,而且它们长的非常相似,那其实这些对象都是 React Element 对象。
var ReactElement = function (type, key, ref, self, source, owner, props) {
var element = {
$$typeof: // REACT_ELEMENT_TYPE, React Element 的标志,是一个Symbol类型。
type: type, // React 元素的类型。
key: key, // React 元素的 key,diff 算法会用到。
ref: ref, // React 元素的 ref 属性,当 React 元素生成实际 DOM 后,返回 DOM 的引用。
props: props, // React 元素的属性,是一个对象。
_owner: owner // 负责创建这个 React 元素的组件。
};
...
return element;
};
function createElement(type, config, children) {
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
function createElementWithValidation(type, props, children) {
var element = createElement.apply(this, arguments);
return element;
}
参数中的 self 和 source 都是只供开发环境下用的参数。从上面的例子我们可以发现唯一不同的就是type 了,对于原生元素,type 是一个字符串类型,记录了原生元素的类型;对于 react 组件来说呢,type 是一个构造函数,或者说它是一个类,记录了这个 react 组件的是哪一个类的实例。所以 <App/>.type === App 的。
所以,每一个包装过后的React元素都是这样的对象
{
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
}
我们再来看一下 createElement 是怎么实现的:
function createElement(type, config, children) {
var propName;
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
// 初始化参数
if (config != null) {
// 初始化ref
if (hasValidRef(config)) {
ref = config.ref;
{
warnIfStringRefCannotBeAutoConverted(config);
}
}
// 初始化key
if (hasValidKey(config)) {
key = '' + config.key;
}
// 初始化self
self = config.__self === undefined ? null : config.__self;
// 初始化source
source = config.__source === undefined ? null : config.__source;
// 所有参数加入props这个对象
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
}
// 初始化Children
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children; // 只有一个子元素,children成为第三个参数
} else if (childrenLength > 1) {
// 有多个子元素,所有子元素合并为数组,children以数组的形式成为第三个参数
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
{
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
// 初始化defaultProps
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
{
if (key || ref) {
var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;
// 定义key的警告
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
// 定义ref的警告
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
-
第一件是初始化 React Element 里的各种参数,例如 type,props 和 children 等。在初始化的时候,会提取出 key,ref 这两个属性,然后 __self,__source 这两个属性也是仅开发用。所以如果你在组件里定义了 key,ref,__self,__source 这4个属性中的任何一个,都是不能在 this.props 里访问到的。从第三个参数开始,传入的参数都会合并为 children 属性,如果只有一个,那么 children 就是第三个元素,如果超过一个,那么这些元素就会合并成一个 children 数组。
-
第二件是初始化 defaultProps,我们可以发现,defaultProps 是通过 type 来初始化的,我们在上面也说过,对于 react 组件来说,type 是 React Element 所属的类,所以可以通过 type 取到该类的 defaultProps(默认属性)。这里还有一点需要注意,如果我们把某个属性的值定义成 undefined,那么这个属性也会使用默认属性,但是定义成 null 就不会使用默认属性。
创建Virtual DOM树
说了这么多,终于到重点了,先来看一下组件结构
App:
<div>
<Header />
<List />
</div>
Header:
<div>
<Logo />
<button>菜单</button>
</div>
List:
<ul>
<li>text 1</li>
<li>text 2</li>
<li>text 3</li>
</ul>
Logo:
<div>
<img src="./foo.png" alt="logo" />
<p>text logo</p>
</div>
ReactDOM.render(<App />, document.getElementById('root'))
通过上面的了解到的 React Element 创建方式,我们不难知道,生成的对应的 Virtual DOM 应该是类似于这样的:
需要注意的是,这些元素并不是真实的 DOM 元素, 它们只是一些对象,而且我们可以看到 React 组件实际上是概念上的形态,最终还是会生成原生的虚拟 DOM 对象。当这些对象上的数据发生变化时,会把变化同步到真实的 DOM 上去。 目前我们可以认为 Virtual DOM 就是这样的一种形态,但是实际上,并没有这么简单,这只是最基本的样子。
待更...
好了,今天先说到这里吧,欢迎吐槽。