什么是虚拟DOM(React16源码分析)
const element = <h1>Hello, world!</h1>;
这个有趣的标签语法既不是字符串也不是 HTML。
它被称为 JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。
为什么使用 JSX?
React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。
JSX 表示对象
Babel 会把 JSX 转译成一个名为 React.createElement()
函数调用。
以下两种示例代码完全等效:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React.createElement()
会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:
// 注意:这是简化过的结构
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
这些对象被称为 “React 元素”(就是我们常说的虚拟DOM)。它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。
我们将探讨如何将 React 元素渲染为 DOM。
实现createElement
createElement代码在react/src/ReactElement.js这个目录里面,下面附上代码,然后依次分析内部结构
1.createElement里面会调用ReactElement,在ReactElement里面只是通过函数封装创建对象的具体过程,这是设计模式里面的工厂模式,这样写的好处就是一眼看上去就知道这个对象有哪些属性。
2.具体的逻辑都在createElement里面3.REACT_ELEMENT_TYPE
是什么,它来自import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
const hasSymbol = typeof Symbol === 'function' && Symbol.for;
export const REACT_ELEMENT_TYPE = hasSymbol
? Symbol.for('react.element')
: 0xeac7;
表示React元素的唯一标识
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
};
// source 是只在开发环境显示的源码映射,这里部分代码被我删减
return element;
};
重点讨论createElement的实现原理,熟悉原理之前你得先掌握它的用法
// 文档在这里:https://zh-hans.reactjs.org/docs/react-api.html#createelement
React.createElement(
type,
[ ],
[ ]
)
创建并返回指定类型的新 React 元素。其中的类型参数既可以是标签名字符串(如 'div' 或 'span'),也可以是 React 组件 类型 (class 组件或函数组件),或是 React fragment 类型。
使用 JSX 编写的代码将会被转换成使用 React.createElement() 的形式。如果使用了 JSX 方式,那么一般来说就不需要直接调用 React.createElement()。
参数1:type表示标签名或React组件
参数2:config表示属性
参数3:children表示子元素
const hasOwnProperty = Object.prototype.hasOwnProperty;
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};
// 判断是否是有效的ref
function hasValidRef(config) {
return config.ref !== undefined;
}
// 判断是否是有效的key
function hasValidKey(config) {
return config.key !== undefined;
}
export function createElement(type, config, children) {
let propName;
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// 把传入的属性都复制到props里面(除了key,ref,__self,__source)!RESERVED_PROPS.hasOwnProperty(propName)就是过滤不要的属性
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// 子元素可能是一个也可能是多个,所以需要分开处理
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];
}
props.children = childArray;
}
// defaultProps 可以为 Class 组件添加默认 props。这一般用于 props 未赋值,但又不能为 null 的情况。
// 文档在这里:https://zh-hans.reactjs.org/docs/react-component.html#defaultprops
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
注意代码中还有:ReactCurrentOwner表示什么,来自import ReactCurrentOwner from './ReactCurrentOwner';
具体代码如下
const ReactCurrentOwner = {
current: null,
currentDispatcher: null,
};
export default ReactCurrentOwner;
这就是虚拟DOM的原理,就是实现一个createElement函数,如果有人问你如何实现虚拟DOM,就把这个函数写出来就可以啦!!!
References
[1]
链接: https://github.com/facebook/react/blob/v16.6.0/packages/react/src/ReactElement.js