ReactElement
# 目录
# JSX
- JSX 是一种将 JS 和 HTML 混合写的语法糖,需要通过 babel 解析之后浏览器才能识别。
- JSX 语法可以通过 @babel/plugin-transform-react-jsx-source (opens new window) 插件进行解析。
例如如下代码:
const Con = () => {
return (
<div style={{color: '#ffffff'}}>
<p class="title">react</p>
<span class="detail">reading</span>
</div>
)
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
将会被解析为:
const Con = () => {
return /*#__PURE__*/React.createElement("div", {
style: {
color: '#ffffff'
}
}, /*#__PURE__*/React.createElement("p", {
class: "title"
}, "react"), /*#__PURE__*/React.createElement("span", {
class: "detail"
}, "reading"));
};
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
如上可知:
- babel 插件在解析 jsx 代码时,js 部分是不需要解析的,html 部分会被解析为
React.createElement
语法。 - 静态的部分会被加上
/*#__PURE__*/
的静态内容标记。 - 多个子节点并不是通过数组传入而是以多个参数的形式传入的,这个可以通过 rest 运算符处理。
# ReactElement
在 react 包中 ReactElement 文件中 导出了 createElement、cloneElement、createFactory、isValidElement 几个关于 ELement 的 API。
# createElement
先来看一个例子,假如一个呗 babel 解析过的 jsx 代码如下:
React.createElement("div", {
class: "class_name",
id: "id_name",
key: "key_name",
ref: "ref_name"
}, React.createElement("span", null, "Tom"), React.createElement("span", null, "Jerry"));
1
2
3
4
5
6
2
3
4
5
6
传入 createElement 函数返回:
{
$$typeof: REACT_ELEMENT_TYPE,
type:'div',
key: 'key_name',
ref: "ref_name",
props: {
class: "class_name",
id: "id_name",
children: [
React.createElement("span", null, "Tom"),
React.createElement("span", null, "Jerry")
]
}
_owner: ReactCurrentOwner.current,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_owner 就是 react 中所谓的 fiber(纤维)。线面我们来看下 createElement 的代码实现:
// 根据元素类型 type,元素属性 config 和元素子节点(数组) children 创建 react 元素
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;
if (config != null) {
// 检查是否添加了 ref 属性
if (hasValidRef(config)) {
ref = config.ref;
}
// 检查是否添加了 key 属性
if (hasValidKey(config)) {
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) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// 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) {
// 单一子节点直接赋值
// children 是放到 props 上的,因此可以通过 props 的 children 获得组件内部内容
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;
}
// Resolve default props
// 元素默认的属性
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,
);
}
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
66
67
68
69
70
71
72
73
74
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
66
67
68
69
70
71
72
73
74
以及 ReactElement 工厂函数:
const ReactElement = function (type, key, ref, self, source, owner, props) {
// 新建一个ReactElement对象
const element = {
// This tag allows us to uniquely identify this as a React Element
// ReactElement 的标志
$$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,
};
return element;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$$typeof
: 这是一个常量,是 react 元素的标志,react 的元素都会带有这个属性。普通的 react 元素$$typeof
的值一般都是REACT_ELEMENT_TYPE
,但是也有特殊,比如 通过ReactDOM.createPortals(child, container)
创建的 portal 元素的值为ReactDOM.createPortals(child, container)
。- type:DOM 元素的类型,如 'div'。
- key:列表元素的唯一标志。
- ref:组件引用变量。
- props:元素属性,包括默认属性、用户定义属性和子元素 children。
- _owner:即 fiber。表示钙元素所从属的 fiber 实例。
# isValidElement
// 校验是否是合法元素,只需要校验类型,重点是判断.$$typeof属性
export function isValidElement(object) {
return (
typeof object === 'object' &&
object !== null &&
// $$typeof是组件的属性,本质是Symbol,ReactElement类型是Symbol,是独有的。
// REACT_ELEMENT_TYPE指的就是Symbol(react.element)
object.$$typeof === REACT_ELEMENT_TYPE // $$typeof: Symbol(react.element)
);
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
是合法的 ReactElement 元素的两个必要条件:
- 类型是 'object',且不是 null;
$$typeof
属性必须是REACT_ELEMENT_TYPE
。
# 小结
- createElement 方法将组件转化成 ReactElement 元素,具有
$$typeof
、type、key、ref、props、_owner 等属性,其中$$typeof
用于对 ReactElement 的类型做判断,type 和 props (包括 children) 用于将 VNode 转化为真实的 DOM,key 和 ref 是组件树中必要元素,而_owner 则记录了当前所属的组件 fiber 实例,用于调和组件的渲染和卸载。 - cloneElement 通过一个给定的 ReactElement 克隆一个 ReactElement。
- isValidElement 判断对象是否是合法的 ReactElement。
编辑 (opens new window)
上次更新: 2022/04/15, 00:23:56