• 作者:老汪软件技巧
  • 发表时间:2024-08-18 11:05
  • 浏览量:

function createElement

extends HTMLAttributes, T extends HTMLElement>( type: keyof ReactHTML, props?: ClassAttributes & P | null, ...children: ReactNode[]): DetailedReactHTMLElement;

他接受三个参数:

createElement 方法的返回值是一个带有如下属性的 React 元素:

使用 createElement 创建的 React 元素有如下特点:

下面为某个元素的 JSX 创建语法与等效的 createElement 函数调用:

function Greeting({ name }) {
  return (
    <h1 className="greeting">
      Hello <i>{name}i>. Welcome!
    h1>
  );
}

import { createElement } from 'react';
function Greeting({ name }) {
  return createElement(
    'h1',
    { className: 'greeting' },
    'Hello ',
    createElement('i', null, name),
    '. Welcome!'
  );
}

createElement 源代码

源码地址:

export function createElement(type, config, children) {
  let propName;
  const props = {};
  let key, ref, self, source = null;
  // 1. 属性处理逻辑: 赋值给 props
  if (config != null)
    // ...........
  // 2. children 处理逻辑
  const childrenLength = arguments.length - 2;
  // ...........
  // 3. 处理标签默认属性
  if (type && type.defaultProps)
    // ............
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props );
}

这个方法本质上是一个预处理器,接收 JSX 编译后的三个参数,分别对他们进行处理,最后创建一个 ReactElement 对象返回。

我们分别看一下 3 个处理过程:

config 逻辑: 处理 key, ref, props

if (config != null) {
  config.ref !== undefined && ref = config.ref;
  // 最终转换为了 str 类型
  config.key !== undefined && key = '' + config.key;
  self = config.__self ?? null;
  source = config.__source ?? null;
  // 只保留必要的属性
  for (propName in config) {
    if (
      // 对象自身是否存在这些属性,不追溯原型链
      hasOwnProperty.call(config, propName) &&
      // 排除 key, ref, __self, __source 这些属性
      !RESERVED_PROPS.hasOwnProperty(propName)
    ) {
      // 转移到 props 属性中
      props[propName] = config[propName];
    }
  }
}

children 逻辑:多个 children 转为数组

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__ && Object.freeze) {
    // DEV 环境封印 children!
    Object.freeze(childArray);
  }
  // 最终放到 props 里面了
  props.children = childArray;
}

type 逻辑: 默认属性加到 props 里面去

// 如果传进来的标签有默认属性,加到 props 里面取
if (type && type.defaultProps) {
  const defaultProps = type.defaultProps;
  for (propName in defaultProps) {
    if (props[propName] === undefined) {
      props[propName] = defaultProps[propName];
    }
  }
}

我没遇到过 defaultProps 情形,或者遇到但忽略了,希望大佬补充一下这种场景

源代码深度解析_源代码在线解析_

ReactElement

这个对象就更简单了,直接上代码():

function ReactElement(type, key, ref, self, source, owner, props) {
  const element = {
    // 一个标识:确定某个对象是 React Element
    $$typeof: REACT_ELEMENT_TYPE, 
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };
  return element;
}

这就返回了一个对象,里面有我们需要的数据。在测试环境做了一些访问限制。

剧透,react 组件冻结 props 和 children 的原因有: 在只读模式下,保证内部数据可靠;在 diff 时,通过浅比较判断是否需要更新组件,提高性能。后文会详细讨论这种做法带来的影响。

这里的 $$typeof 标识会在如下方法中使用:

export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}

我们在写 react 组件时,常用到 ClassComponent 和 FunctionComponent。他们所对应的 type 分别是 AppClass,AppFunc。例如某某个函数组件对应的 Element 结构可能如下:

{
  $$typeof: Symbol(react.element),
  key: null,
  props: {},
  ref: null,
  type: ƒ AppFunc(),
  _owner: null,
  _store: {validated: false},
  _self: null,
  _source: null 
}

由于这两种形式的组件 type 都是 Function 类型,可以这样判断是否为类组件:

ClassComponent.prototype.isReactComponent = {};

JSX 与 vDOM

上文理了一遍 JSX -> ReactElement 的过程,总结一下,这个过程将 JSX 语法转换为了一个对象,这个对象包含的数据及注意点如下:

{
  $$typeof: REACT_ELEMENT_TYPE, // 标识它是一个 ReactElement
  type: type, // 标记它的类型,函数组件,类组件,H5原生标签
  key: key, // 优化用的 key,一个字符串
  ref: ref, // 一个引用
  props: props, // 元素属性,children 也在里面。会被冻结
  _owner: owner, // 指向父元素
}

这是一个完整的 ReactElement 但还不是 vDOM。要想在 Fiber 架构进行更新,它还缺少了如下内容:

在组件 mount 过程中,Reconciler 会根据 JSX 描述的内容生产对应的 vDOM。在 update 过程中,则将 JSX 与现有的 vDOM 数据对比,生成对应的 vDOM,根据结果为 vDOM 打上标记。

ReactElement 的结构属性

为了之后方便理解 Fiber 树结构,这里简单说明一下 ReactElement 的结构属性:_owner 与 props.children。

那么一个简单的 ReactElement 树结构可以是: