ReactElement
一、JSX到ReactElement的转化
利用babel插件将编写的JSX代码转化为React.createElement代码:
function Cmp(){
return <a>123</a>
}
<Cmp id="app">
<span>1</span>
<span>2</span>
</Cmp>
编译后得React代码:
function Cmp() {
return React.createElement("a", null, "123");
}
const jsx = React.createElement(
Cmp,
{ id: "app" },
React.createElement("span", null, "1"),
React.createElement("span", null, "2")
);
加上render的示例:
// 转换前
render() {
return (
<div className="container">
<h1 className="title">React learning</h1>
<List data={this.state.data} />
</div>
);
}
// 转换后
render() {
return React.createElement("div", {
className: "container"
},
React.createElement("h1", { className: "title"}, "React learning"),
React.createElement(List, { data: this.state.data }));
}
二、ReactElement源码解读
1、React源码入口
packages\react\src\React.js
2、ReactElement源码入口
packages\react\src\ReactElement.js
3、createElement核心代码
/**
* @param type 表示当前节点的类型,可以是原生的DOM标签字符串,
* 也可以是函数定义组件或者其它类型,如果是组件第一个字母大写区分
* @param config 表示当前节点的属性配置信息
* @param children 表示当前节点的子节点,可以不传,也可以传入原始的字符串文本,甚至可以传入多个子节点
* @returns 返回的是一个ReactElement对象
*/
export function createElement(type, config, children) {
// 声明一堆变量
let propName;
// 用于存放config中的属性,但是过滤了一些内部受保护的属性名
const props = {};
// 将config中的key和ref属性使用变量进行单独保存
let key = null;
let ref = null;
let self = null;
let source = null;
// config为null表示节点没有设置任何相关属性
if (config != null) {
// 验证ref有效性 config.ref !== undefined
if (hasValidRef(config)) {
ref = config.ref;
}
// 验证key有效性 config.key !== undefined
if (hasValidKey(config)) {
key = "" + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// 用于将config中的所有属性在过滤掉内部受保护的属性名后,将剩余的属性全部拷贝到props对象中存储
// const RESERVED_PROPS = {
// key: true,
// ref: true,
// __self: true,
// __source: true,
// };
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// 由于子节点的数量不限,因此从第三个参数开始,判断剩余参数的长度,具有多个子节点则props.children属性存储为一个数组
const childrenLength = arguments.length - 2; // 减2是排除type, config
if (childrenLength === 1) {
// 单节点的情况下props.children属性直接存储对应的节点
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;
}
// 此处用于解析静态属性defaultProps
// 针对于类组件或函数定义组件的情况,可以单独设置静态属性defaultProps
// 如果有设置defaultProps,则遍历每个属性并将其赋值到props对象中(前提是该属性在props对象中对应的值为undefined)
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
// 返回ReactElement对象
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props
);
}
1、核心逻辑:调用一个方法返回一个element的核心对象,包含各种属性、key等。
2、在类组件的render方法中最终返回的是由多个ReactElement对象组成的多层嵌套结构,所有的子节点信息均存放在父节点的props.children属性中
/**
* 为一个工厂函数,每次执行都会创建并返回一个ReactElement对象
* @param type 表示节点所对应的类型,与React.createElement方法的第一个参数保持一致
* @param key 表示节点所对应的唯一标识,一般在列表渲染中我们需要为每个节点设置key属性
* @param ref 表示对节点的引用,可以通过React.createRef()或者useRef()来创建引用
* @param self 该属性只有在开发环境才存在
* @param source 该属性只有在开发环境才存在
* @param owner 一个内部属性,指向ReactCurrentOwner.current,表示一个Fiber节点
* @param props 表示该节点的属性信息,在React.createElement中通过config,children参数和defaultProps静态属性得到
* @returns 返回一个ReactElement对象
*/
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// 每个对象的唯一标识-symbol常量
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
};
return element;
};
1、ReactElement对象主要增加了一个$$typeof属性用于标识该对象是一个React Element类型。
2、REACT_ELEMENT_TYPE在支持Symbol类型的环境中为symbol类型,否则为number类型的数值。
3、与REACT_ELEMENT_TYPE对应的还有很多其他的类型,均存放在shared/ReactSymbols目录中
————————————————
智一面|前端面试必备练习题