😆 Hi,大家好,我是张玥卿云,一个面向未来编程的程序员!准备将 React 源码解析写成一系列文章分享给大家,希望大家喜欢~
一、前言
一个最简单的 React 程序往往包含三个步骤:
- 创建 React 元素树:
const element = React.createElement("div", {}, '');
- 创建 FiberRoot 节点并挂载到 DOM 容器节点上:
const root = ReactDOM.createRoot(container);
- FiberRoot 渲染第一步创建的 React 元素树:
root.render(element);
今天我们详细讲解的是 React 元素树的创建。
二、React 元素树
React 元素树是开发者和 React 交互的桥梁,元素树描述了目标网站的全貌, React 会解析元素树,转化为Fiber 架构,生成交互式的网页。
React 元素树由 React 元素构成,React 元素是 $$typeof 属性为 REACT_ELEMENT_TYPE 的对象,它由 ReactElement 工厂函数创建。它还具有 type,key,ref,props 四个基本属性:
| 属性 | 介绍 |
|---|---|
| type | 元素的类型, 可以为 HTML 元素或 React 组件 |
| key | 元素在其同级兄弟中的的身份标志(唯一键值) |
| ref | 对组件实例的引用 |
| props | 组件或 HTML 元素的属性值 |
React 元素还具有 _owner, _store, _self, _source 三个内部属性,其中 _owner 属性记录了创建该元素的起因,而 _store, _self 和 _source 在开发模式下才会被创建。
下面是 ReactElement 工厂函数的源码:
/** 创建 React 元素的工厂函数 */
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
/** 标识该对象为 React 元素 */
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner
}
/** ... 此处省略 DEV 模式下的若干行代码 ... */
return element;
}
三、React 元素的创建
React.createElement 和 React.cloneElement 是我们熟知的可以获取 React 元素的 Api:
/** 创建一个 React 元素 */
React.createElement(<MyElement />, {elementProp: 'element-prop'}, children);
/** 克隆一个 React 元素 */
React.cloneElement(element, {newProp: 'new-prop'});
下面我们分别看看这两个 Api 的源码。
3.1 React.createElement
React.createElement 是最常见的创建 React 元素的 Api,它的执行流程是:
- 解析形参 config 获取变量 ref、key、self、 source、 props 和 props.children。
- 根据 defaultProps 初始化变量 props。
- 利用 1 中变量和 ReactElement 工厂函数创建 React 元素。
源代码如下:
export function createElement(type, config, children) {
let propName;
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
if(config != null) {
/** 初始化 ref */
if(hasValidRef(config)) {
ref = config.ref;
}
/** 初始化 key */
if(hasValidKey(config)) {
key = '' + config.key;
}
}
/** 初始化 self 和 source */
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
/** 初始化其他 props */
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
/** 初始化 children */
const childrenLength = arguments.length - 2;
if(childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childrenArray = Array(childrenLength);
for(let i = 0; i < childrenLength; i++) {
childrenArray = Array(childrenLength);
}
props.children = childrenArray;
}
/** 初始化 defaultProps */
if(type && type.defaultProps) {
const defaultProps = type.defaultProps;
for(propName in defaultProps) {
if(props[Name] in defaultProps) {
props[propName] = defaultProps[propName];
}
}
}
/** 创建 React 元素 */
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props
)
}
3.2 React.cloneElement
React.cloneElement 和 React.createElement 比较相似,我们经常用它来扩展已存在的 React 元素的功能,这个 api 主要做了以下几件事:
- 从原 React 元素中获取 props、key、ref、_self、_source、_owner 属性。
- 解析新配置,获取新的 ref、key、defaultProps 等属性并覆盖原属性。
- 根据 defaultProps 和 props 设置默认值。
- 获取 props.children。
- 通过工厂函数 ReactElement 创建 React 元素并返回。
源代码如下:
export function cloneElement(element, config, children) {
if(element === null || element === undefined) {
throw new Error(`React.cloneElement(...): The argument must be a React element, but you passed ${element}`)
}
let propName;
// 获取待克隆对象的 props
const props = assign({}, element.props);
/** 获取待克隆对象的 key */
let key = element.key;
/** 获取待克隆对象的 ref*/
let ref = element.ref;
/** 获取待克隆对象的 _self、 _source、_owner 属性 */
const self = element._self;
const source = element._source;
let onwer = element._owner;
/** 解析新的配置 */
if(config != null) {
/** 获取新的 ref */
if(hasValidRef(config)) {
ref = config.ref;
owner = ReactCurrentOwner.current;
}
/** 获取新的 key */
if(hasValidKey(config)) {
key = '' + config.key;
}
/** 获取 defaultProps */
let defaultProps;
if(element.type && element.type.defaultProps) {
defaultProps = element.type.defaultProps;
}
/** 根据新的 defaultProps 和从被克隆元素获取的 props 初始化 props */
for(propName in config) {
if(
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
if(config[propName] === undefined && defaultProps !== undefined) {
props[propName] = defaultProps[propName];
} else {
props[propName] = config[propName];
}
}
}
/** 获取 children */
const childrenLength = arguments.length - 2;
if(chidlrenLength === 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;
}
/** 通过工厂函数创建 React 元素并返回 */
return ReactElement(element.type, key, ref, self, source, owner, props);
}
}
四、结尾
这些就是 ReactElement 元素树的创建过程,如有疑惑,欢迎大家一起讨论~ @V@ ~
😈 如感兴趣,可联系本人:张玥卿云
- 电话: 16602927079
- 邮箱: zhangyueqingyun@foxmail.com
- 微信: abcde-ovo-yz