本篇主要基于源码谈谈jsx被编译之后,react在创建react element时做了什么
关于jsx的基础知识可以看看另一篇博客由浅入深理解jsx
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, ... }
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)) ); }
如果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; } ... }
先为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]; } } } ... }
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。