「02」JSX转换

196 阅读2分钟

我们可以在babel的playground看到jsx代码转换后的东西,当前是automatic模式,jsx代码会被转换成_jsx方法的调用,这是react17之后新增的。

而传统的也就是17之前是转换成React.createElement的调用

从上面我们可以看到_jsx函数参数如下:

  • 第一个参数是标签名也可以说是类型的字符串,
  • 第二个参数应该是一些标签上的配置对象
  • 第三个参数可以认为是标签的children

明确目标

首先要明确我们的目标,JSX转换的过程包括两部分

  • 编译时:这个阶段babel已经帮我们完成了
  • 运行时:这个阶段需要我们来完成,也就是实现jsx方法或React.createElement方法

所以目前我们只需要实现jsx方法,并且能打包成功

本章目录结构如下

第一步 实现ReactElement

当前目标是实现ReactElement函数,传入typekeyrefprops,组装成新的对象element,包含$$typeoftypekeyrefprops,为了区别于官方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中的keyref是需要我们单独处理的,当遇到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;
	}
}

接着来处理childrenchildren的话也会有只有一个的情况,和有多个的情况,当只有一个的时候我们直接从数组中取出来

const maybeChildrenLength = maybeChildren.length;
if (maybeChildrenLength) {
	// 当length===1只有一个
	if (maybeChildrenLength === 1) {
		props.children = maybeChildren[0];
	} else {
		props.children = maybeChildren;
	}
}

最后再调用ReactElement传入已经处理过的typekeyrefprops

return ReactElement(type, key, ref, props);

所有代码:github.com/ohlyf/oh-re…