在React中我们使用JSX去描述UI
如下所示:
import React from 'react';
function App() {
return <h1>Hello World</h1>;
}
从JSX到JS的转换
对于浏览器而言,它只会理解javascript代码所以需要对代码进行转换,将JSX转变为JS代码:
在React17之前是由React包内部的createElement函数去实现的;
import React from 'react';
function App() {
return React.createElement('div', config, 'Hello world!');
}
而在此之后这个转换过程在编译的时候会使用Babel进行处理。
import { jsx } from 'react/jsx-runtime';
function App() {
return jsx('div', { children: 'Hello world!' });
}
当我们使用createElement去处理节点时得到的是一个React元素,其数据类型被定义为ReactElement,其结构如下:
const element = {
$$typeof: REACT_ELEMENT_TYPE, // $$typeof:Symbol类型的值用于标记该元素为ReactElement类型
type, // 元素类型
key, // 唯一标识
ref, // 实例
props, // 属性值
};
为了方便处理React元素, 使用一个函数去构造ReactElement
function ReactElement(
type,
key,
ref,
props,
) {
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref,
props,
};
return element;
}
createElement
在createElement内部我们需要做的就是将如下调用,转换为React元素
React.createElement('div', null, 'Hello world!')
以下是一个简化版的实现:
function hasValidKey(config){
return config.key !== undefined;
}
function hasValidRef(config){
return config.ref !== undefined;
}
export function createElement (type, config, children) {
let propName;
const props = {};
let key = null;
let ref = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.key;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
// 将除key和ref之外的值放入props中
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
propName !== 'key' && propName !== 'ref'
) {
props[propName] = config[propName];
}
}
}
const childrenLength = arguments.length - 2;
// 将子节点添加到props的children属性中
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;
}
// 处理defaultProps
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
// 返回处理后的节点
return ReactElement(
type,
key,
ref,
props,
);
}
函数的主要处理流程包括:
- 接收参数:函数接收三个参数,
type表示元素的类型,config是一个对象,包含元素的属性(props)、键(key)和引用(ref),children是元素的子元素。 - 处理属性:函数会从
config对象中提取出key和ref,并将其分别存储在独立的变量中。然后,它会将config对象中剩余的属性复制到一个新的props对象中。 - 处理子元素:函数会检查传递给
createElement的子元素数量。如果有多个子元素,它们会被放入一个数组中,然后作为props.children属性添加到props对象中。 - 应用默认属性:如果
type对象定义了defaultProps,函数会将这些默认属性应用到props对象中,填补任何未在config中显式定义的属性。 - 创建并返回React元素:最后,函数会使用
type、key、ref和props对象来创建一个React元素,并将其返回。这个元素可以被React DOM或其他渲染器用来在页面上渲染实际的DOM元素。
总结
JSX到JS的转换过程:
- JSX以声明式的方式编写React组件。
- 通过
createElement或jsx将JSX调用转换为React元素,包括提取属性、处理子元素、应用默认属性,并最终返回一个React元素对象。