序
我们可以在babel的playground看到jsx代码转换后的东西,当前是automatic模式,jsx代码会被转换成_jsx方法的调用,这是react17之后新增的。
而传统的也就是17之前是转换成React.createElement的调用
从上面我们可以看到_jsx函数参数如下:
- 第一个参数是标签名也可以说是类型的字符串,
- 第二个参数应该是一些标签上的配置对象
- 第三个参数可以认为是标签的
children
明确目标
首先要明确我们的目标,JSX转换的过程包括两部分
- 编译时:这个阶段
babel已经帮我们完成了 - 运行时:这个阶段需要我们来完成,也就是实现
jsx方法或React.createElement方法
所以目前我们只需要实现jsx方法,并且能打包成功
本章目录结构如下
第一步 实现ReactElement
当前目标是实现ReactElement函数,传入type、key、ref、props,组装成新的对象element,包含$$typeof、type、key、ref、props,为了区别于官方React,我们新增element的属性__mark:'ohlyf'
在react/src/jsx.ts中新建ReactElement函数
const ReactElement = function (
type: any,
key: any,
ref: any,
props: any
): ReactElementType {
const element = {
$$typeof: xxx,
type,
key,
ref,
props,
__mark: 'ohlyf'
};
return element;
};
为了防止ReactElement滥用我们需要将ReactElement定义为一个特殊值,即$$typeof需要是特殊值,进行唯一标识
在shared/reactSymbols.ts中新增如下代码
判断浏览器是否支持symbol,如果不支持的话就使用一个特殊符号0xeac7
// 判断是否支持symbol
const supportSymbol = typeof Symbol === 'function' && Symbol.for;
export const REACT_ELEMENT_TYPE = supportSymbol
? Symbol.for('react.element')
: 0xeac7;
接着我们需要定义ReactElement需要用到的类型
在shared/ReactTypes.ts新增如下代码
export type Type = any;
export type Key = any;
export type Ref = any;
export type Props = any;
export interface ReactElementType {
$$typeof: symbol | number;
type: Type;
key: Key;
props: Props;
ref: Ref;
__mark: string;
}
回到react/src/jsx.ts中完善ReactElement的类型
const ReactElement = function (
type: Type,
key: Key,
ref: Ref,
props: Props
): ReactElementType {
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref,
props,
__mark: 'ohlyf'
};
return element;
};
第二步 实现jsx函数
从上面我们已经知道jsx接收两个及以上参数,第一个type,第二个config,剩余的参数都视为children
export const jsx = (type: Type, config: any, ...maybeChildren: any) => {}
config中的key、ref是需要我们单独处理的,当遇到key的时候,给key进行赋值,当遇到ref的时候给ref进行赋值,然后再判断这个属性是不是对象自身的,如果是即可传入props中
// 单独处理key 和 ref
let key: Key = null;
const props: Props = {};
let ref: Ref = null;
for (const prop in config) {
const val = config[prop];
if (prop === 'key') {
if (val !== undefined) {
key = '' + val;
}
continue;
}
if (prop === 'ref') {
if (val !== undefined) {
ref = val;
}
continue;
}
if ({}.hasOwnProperty.call(config, prop)) {
props[prop] = val;
}
}
接着来处理children,children的话也会有只有一个的情况,和有多个的情况,当只有一个的时候我们直接从数组中取出来
const maybeChildrenLength = maybeChildren.length;
if (maybeChildrenLength) {
// 当length===1只有一个
if (maybeChildrenLength === 1) {
props.children = maybeChildren[0];
} else {
props.children = maybeChildren;
}
}
最后再调用ReactElement传入已经处理过的type、key、ref、props
return ReactElement(type, key, ref, props);