ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2022-04-11 19:31:29  阅读:182  来源: 互联网

标签:REACT react ReactElement TYPE React 源码 props const type


一、前言

本篇主要基于源码谈谈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,react,ReactElement,TYPE,React,源码,props,const,type
来源: https://www.cnblogs.com/ltfxy/p/16131553.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有