Fancy Front End Fancy Front End
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)

Jonsam NG

让有意义的事变得有意思,让有意思的事变得有意义
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)
  • 开始上手
  • plan 计划
  • 基础

    • 开始上手
    • 章节说明
    • F&Q
    • ReactChildren
    • ReactElement
      • 目录
      • JSX
      • ReactElement
  • 调和(Reconciliation)

  • 调度器(Scheduler)

  • 更新器(Updater)

  • 渲染器(Render)

  • hooks原理

  • 总结

  • React源码漂流记

  • react
  • 基础
jonsam
2022-04-14
目录

ReactElement

# 目录

  • 目录
  • JSX
  • ReactElement
    • createElement
    • isValidElement
    • 小结

# 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

将会被解析为:

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

如上可知:

  • 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

传入 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

_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

以及 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
  • $$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

是合法的 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
ReactChildren
开始上手

← ReactChildren 开始上手→

最近更新
01
渲染原理之组件结构与 JSX 编译
09-07
02
计划跟踪
09-06
03
开始上手
09-06
更多文章>
Theme by Vdoing | Copyright © 2022-2022 Fancy Front End | Made by Jonsam by ❤
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式