React源码解读(二) —— React.createElement

在说createElement之前,首先来比较下下面两种写法。

1
2
3
4
5
6
7
8
9
10
11

//写法一
let pDom=document.createElement("p");
let textNode=document.createTextNode("text");
pDom.appendChild(textNode);
let rootElement=document.getElementById("root");
rootElement.appendChild(pDom);

//写法二
let pDom = <p>text</p>
return <div id='root'>{pDom}</div>

以上两种写法分别是js写法和react写法,很显然,相较于写法一,写法二更符合我们书写html的习惯而且代码也更容易阅读。
那么为什么能用写法二来写呢,我们把代码放进babel里面看一下。

1
2
3
4
5

var pDom = React.createElement("p", null, "text");
return React.createElement("div", {
id: "root"
}, pDom);

可以看到写法二的代码被转成了上述的样子,即,用React.createElement创建了dom节点(虚拟)。我们可以打印看下这个pDom是个什么东西。

pDom

从中可以看出pDom实际上就是一个js对象,有如下属性。

  • $$typeof 唯一标志,它是一个Symbol对象,具有唯一性,在redux中给actions命名的时候用起来很爽。
  • key DOM结构标识,提升diff算法性能。
  • props 标签属性。
  • ref 标志真实Dom引用。
  • type 该虚拟dom的标签类型。
  • _owner 16版本新加的东西,是个FiberNode对象,直译就是纤维节点,可以理解成片段节点。
  • _store 暂时不清楚(源码里也没看懂用来干嘛的)
    所以可以这样理解,虚拟节点就是react对dom的一个解析并将其用js对象表示出来。

现在来看下createElement的源码。

首先来看下入参。

1
2
3
4
5
6
7
8
9
const RESERVED_PROPS = {    //需要过滤的propNames
key: true,
ref: true,
__self: true,
__source: true,
};
export function createElement(type, config, children) {
...
}

结合之前看的babel编译后的代码我们可以看出来type,config,children代表什么,type是指节点,config是指传给该标签的属性,children是子节点。
接下来看内部代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
let propName;

// Reserved names are extracted
const props = {};

let key = null;
let ref = null;
let self = null;
let source = null;

if (config != null) {
if (hasValidRef(config)) { //判断config中是否存在ref
ref = config.ref;
}
if (hasValidKey(config)) { //判断config中是否存在key
key = '' + config.key;
}

self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;

// Remaining properties are added to a new props object
for (propName in config) { //遍历属性并将其添加到props里面
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName) //这里是过滤不需要绑定的propNames
) {
props[propName] = config[propName];
}
}
}

再接下来就是children的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
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); //es6语法,freeze方法可以冻结一个对象使其无法被操作
}
}
props.children = childArray;
}

在接下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) { //将defaultProps添加到props里面
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
if (key || ref) {
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown' //标签名的获取
: type;
if (key) {
defineKeyPropWarningGetter(props, displayName); //用来把key添加中props中。
}
if (ref) {
defineRefPropWarningGetter(props, displayName); //用来把ref添加中props中。
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function defineKeyPropWarningGetter(props, displayName) {
const warnAboutAccessingKey = function() {
if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true;
warningWithoutStack(
false,
'%s: `key` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://fb.me/react-special-props)',
displayName,
);
}
};
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, 'key', { //在props中添加一个key属性,并将get和configurable赋给key
get: warnAboutAccessingKey,
configurable: true,
});
}

function defineRefPropWarningGetter(props, displayName) {
const warnAboutAccessingRef = function() {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
warningWithoutStack(
false,
'%s: `ref` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://fb.me/react-special-props)',
displayName,
);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, 'ref', { //在props中添加一个ref属性,并将get和configurable赋给ref
get: warnAboutAccessingRef,
configurable: true,
});
}

最后就是返回一个ReactElement对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);

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__) {
// The validation flag is currently mutative. We put it on
// an external backing store so that we can freeze the whole object.
// This can be replaced with a WeakMap once they are implemented in
// commonly used development environments.
element._store = {};

// To make comparing ReactElements easier for testing purposes, we make
// the validation flag non-enumerable (where possible, which should
// include every environment we run tests in), so the test framework
// ignores it.
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false,
});
// self and source are DEV only properties.
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self,
});
// Two elements created in two different places should be considered
// equal for testing purposes and therefore we hide it from enumeration.
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source,
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}

return element;
};

以上

觉得不错的话可以打赏哦