- 作者:老汪软件技巧
- 发表时间:2024-08-18 11:05
- 浏览量:
function createElementextends 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 树结构可以是: