深入React源码解读ReactElement到底做了什么

2022/4/11 20:42:35

本文主要是介绍深入React源码解读ReactElement到底做了什么,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、前言

本篇主要基于源码谈谈jsx被编译之后,react在创建react element时做了什么
关于jsx的基础知识可以看看另一篇博客由浅入深理解jsx

二、关于CreateElement

jsx被babel等编译工具转换之后,实质上是React.createElement方法。在react/packages/react/src/React.js文件中,我们可以发现这个方法有两个来源,在开发模式下,会使用携带校验功能的cloneElementWithValidation,否则使用ReactElement模块定义的createElement

import {
  createElement,
} from './ReactElement';
import {
  cloneElementWithValidation,
} from './ReactElementValidator';
const React = {
  ...
  createElement: __DEV__ ? createElementWithValidation : createElement,
  ...
}

二、类型校验

1. 校验react元素类型

react内部采用Symbol对react元素类型进行区分,如果环境不支持ES6 Symbol,那么使用16进制数字去polyfill

const hasSymbol = typeof Symbol === 'function' && Symbol.for;

// react元素类型
export const REACT_ELEMENT_TYPE = hasSymbol
  ? Symbol.for('react.element')
  : 0xeac7;
export const REACT_PORTAL_TYPE = hasSymbol
  ? Symbol.for('react.portal')
  : 0xeaca;
export const REACT_FRAGMENT_TYPE = hasSymbol
  ? Symbol.for('react.fragment')
  : 0xeacb;
export const REACT_STRICT_MODE_TYPE = hasSymbol
  ? Symbol.for('react.strict_mode')
  : 0xeacc;
export const REACT_PROFILER_TYPE = hasSymbol
  ? Symbol.for('react.profiler')
  : 0xead2;
export const REACT_PROVIDER_TYPE = hasSymbol
  ? Symbol.for('react.provider')
  : 0xeacd;
export const REACT_CONTEXT_TYPE = hasSymbol
  ? Symbol.for('react.context')
  : 0xeace;
export const REACT_CONCURRENT_MODE_TYPE = hasSymbol
  ? Symbol.for('react.concurrent_mode')
  : 0xeacf;
export const REACT_FORWARD_REF_TYPE = hasSymbol
  ? Symbol.for('react.forward_ref')
  : 0xead0;
export const REACT_SUSPENSE_TYPE = hasSymbol
  ? Symbol.for('react.suspense')
  : 0xead1;
export const REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3;
export const REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4;

// 校验元素类型是否合理
export default function isValidElementType(type: mixed) {
  return (
    typeof type === 'string' ||
    typeof type === 'function' ||
    // Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill.
    type === REACT_FRAGMENT_TYPE ||
    type === REACT_CONCURRENT_MODE_TYPE ||
    type === REACT_PROFILER_TYPE ||
    type === REACT_STRICT_MODE_TYPE ||
    type === REACT_SUSPENSE_TYPE ||
    (typeof type === 'object' &&
      type !== null &&
      (type.$$typeof === REACT_LAZY_TYPE ||
        type.$$typeof === REACT_MEMO_TYPE ||
        type.$$typeof === REACT_PROVIDER_TYPE ||
        type.$$typeof === REACT_CONTEXT_TYPE ||
        type.$$typeof === REACT_FORWARD_REF_TYPE))
  );
}

三、属性添加

1. children

如果root只有一个子节点,那么直接把它作为props.children
如果root没有子节点,那么props.children为undefined
如果子节点个数大于1,那么提取形参列表将子节点浅拷贝到childArray,将其作为props.children
在实际开发中,这种方式在处理单个子节点的组件情况下是较好的,相比之下它不必每次遍历形参列表,时间复杂度从O(n)降级到O(1)

因此,在使用props.children时需要注意的是,props.children可能的类型为undefined、ReactElement、Array。应对这种情况官方推出了React.children这个API,如React.children.map/forEach,它在底层对props.children类型做了兼容处理,是更为推荐的操作props.children的方式

export function createElement(type, config, 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];
    }
    props.children = childArray;
  }
  ...
}

2. 处理props与defaultProps

先为props添加属性,然后用defaultProps混入props,仅当props中对应属性为undefined时才会将其覆盖
defaultProps作用是在props未声明某属性时,为其提供默认值

export function createElement(type, config, children) {
  ...
  // 处理props
  for (propName in config) {
    if (
      hasOwnProperty.call(config, propName) &&
      // RESERVED_PROPS 是一些内建属性,比如key和ref,它们是react元素上的属性,不会被添加到props中
      !RESERVED_PROPS.hasOwnProperty(propName)
    ) {
      props[propName] = config[propName];
    }
  }

  // 处理defaultProps
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  ...
}

四、生成ReactElement

createElement最终会调用ReactElement方法,该方法会将react元素的类型标记为REACT_ELEMENT_TYPE并返回一个object

export function createElement(type, config, children) {
  ...
  return ReactElement(
    type,
    key,
    ref,
    self, // 辅助属性,用于记录this
    source, // 源信息,用于存储行数,文件名等等
    ReactCurrentOwner.current, // Fiber对象
    props,
  );
  ...
}

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner
  };
  return element;
};

最后我们得到一个结构如下所示的对象
在这里插入图片描述

文中代码关联的React版本: 16.8。



这篇关于深入React源码解读ReactElement到底做了什么的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程