React 源码之 createElement() 解读

发布于 2023-05-06 21:01:16 字数 8055 浏览 94 评论 0

配图源自 Freepik

今天一起来看下 React.createElement() 方法

React.createElement

中文 or 英文文档

React 不强制要求使用 JSX,每个 JSX 元素只是调用 React.createElement(type, [props], [...children]) 的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。

React.createElement(
  type,
  [props],
  [...children]
)
  • type 可以是标签名字符串(如 div 或 span 等),也可以是 React 组件类型(class 组件或函数组件),或者是 React fragment 类型。
  • props 可选,组件属性
  • children 可选,子元素。含有多个子元素时,最终 React Element 的 props.children 会返回一个数组。

例如:使用 JSX 编写代码:

class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.toWhat}</div>
  }
}

ReactDOM.render(
  <Hello toWhat="World" />,
  document.getElementById('root')
)

也可以编写不使用 JSX 的代码:

class Hello extends React.Component {
  render() {
    return React.createElement(
      'div',
      null,
      `Hello ${this.prorps.toWhat}`
    )
  }
}

ReactDOM.render(
  React.createElement(Hello, { toWhat: 'World' }, null),
  document.getElementById('root')
)

如果你想了解更多 JSX 转换为 JavaScript 的示例,可以尝试使用 在线 Babel 编译器

看源码

function createElement(type, config, children) {
  var propName;

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

  // 一些保留的属性
  var key = null;
  var ref = null;
  var self = null;
  var source = null;

  // 提取 key、ref、self、source、prop
  if (config != null) {
    // 将合法的 ref 赋值给 ref 变量
    if (hasValidRef(config)) {
      ref = config.ref;

      {
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }

    // 将合法的 key 转换为字符串类型,并赋值给变量 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
    // 将 config 中除 key、ref、__self、__source 之外的 prop 提取出来,并放入 props 变量中
    for (propName in config) {
      if (hasOwnProperty$1.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.
  // 入参的前两个 type 和 config,剩余的都是 children 参数。所以减 2
  var childrenLength = arguments.length - 2;

  if (childrenLength === 1) {
    // 只有一个子元素时,直接挂到 props.children 下(非数组形式)
    props.children = children;
  } else if (childrenLength > 1) {
    // 子元素多于一个时,将它们都放入数组中,然后挂到 props.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;
  }

  // Resolve default props
  // 取出组件类(即 type 不为字符串的情况)中的静态属性 defaultProps,并给未在 JSX 中设置值的属性设置默认值。
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;

    for (propName in defaultProps) {
      // 注意下,若属性值为 null 并不会触发设置默认值的处理,仅 undefined。
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }

  {
    // 以下步骤主要是避免一些保留属性被错误取到,提供警告
    if (key || ref) {
      var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;

      if (key) {
        defineKeyPropWarningGetter(props, displayName);
      }

      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }

  // 调用 ReactElement 构建元素,并返回
  // type 是直接透传的,
  // key、ref 等等都是从 config 里面解析出来的,props 是除去一些保留属性之外在 config 上读取的属性
  // children 是子元素,多个时返回数组
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}

接着看 ReactElement 源码:

/**
 * Factory method to create a new React element. This no longer adheres to
 * the class pattern, so do not use new to call it. Also, instanceof check
 * will not work. Instead test $$typeof field against Symbol.for('react.element') to check
 * if something is a React Element.
 *
 * @param {*} type
 * @param {*} props
 * @param {*} key
 * @param {string|object} ref
 * @param {*} owner
 * @param {*} self A *temporary* helper to detect places where `this` is
 * different from the `owner` when React.createElement is called, so that we
 * can warn. We want to get rid of owner and replace string `ref`s with arrow
 * functions, and as long as `this` and owner are the same, there will be no
 * change in behavior.
 * @param {*} source An annotation object (added by a transpiler or otherwise)
 * indicating filename, line number, and/or other information.
 * @internal
 */
var ReactElement = function (type, key, ref, self, source, owner, props) {
  // 我们暂时把下面的代码折叠,可以看到 ReactElement 方法最后返回的就是 element 对象
  var element = {
    // This tag allows us to uniquely identify this as a React Element
    // 通过这个标签来识别react的元素(一个 Symbol)
    $$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
  };

  {
    // 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.
    // 为了更加方便地进行测试,我们设置了一个不可枚举的验证标志位,以便测试框架忽略它
    // 给 _store 设置 validated 属性 false
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false
    });

    // self and source are DEV only properties.
    // self 和 source 都是开发环境才存在的
    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
    });

    // 如果 Object 有 freeze 的实现,我们冻结元素和它的属性
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

注意,React 内部是使用 $$typeof 来判断 react 元素的类型的。使用 _store 来记录内部的状态,后面会有用到。为了方便测试框架,_store 中定义了不可配置不可枚举的 validated 属性。类似的,框架内部定义了 selfsource 的副本 _self_source,他们都是不可配置不可枚举不可写的。

最近在重新学习 React,也会去看一些源码什么的,在 GitHub 上建了个仓库(react-learn)去记录学习的东西,然后把其中的一些挑选出来发文了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

递刀给你

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

eins

文章 0 评论 0

世界等同你

文章 0 评论 0

毒初莱肆砂笔

文章 0 评论 0

初雪

文章 0 评论 0

miao

文章 0 评论 0

qq_zQQHIW

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文