从0实现React18系列一
本系列是讲述从0开始实现一个react18的基本版本。由于React
源码通过Mono-repo 管理仓库,我们也是用pnpm
提供的workspaces
来管理我们的代码仓库,打包我们使用rollup
进行打包。
系列文章:
- React实现系列一 - jsx
- 从0实现React18系列二-reconciler
- 从0实现React18系列三-打标记
- 从0实现React18系列四-commit
- 从0实现React18系列五-update流程
- 从0实现React18系列六-dispatch update流程
- 从0实现React18系列七-事件系统
- 从0实现React18系列八-同级节点diff
基本概念
我们知道React
是一个应用级框架,每一次更新都是从应用根节点开始向下调和,相比于元素级Svelte
和组件级的Vue
, React
甚至不需要确定哪一个自变量发生了变化。基于React
的内部优化以及提供开发者一些减少无意义的遍历过程,性能也十分优秀。
React
主要是将页面的结构通过jsx进行描述,在调和后,每一个 React element
对象的子节点都会形成一个对应的fiberNode
。
本节内容主要是实现jsx
的生成。在React
的源码中,jsx
的代码逻辑存在packages下面的react
包中。为了兼容React
的旧版本,我们主要是实现最后导出三个文件。
index.js
:import React from 'react'
这样使用jsx-runtime.js
: 新版通过babel
导入jsx-dev-runtime.js
: 开发环境的包
jsx
为了开发者方便,React
提供一种类似于html的方式去书写代码,然后React
通过babel去进行转义。在React
的新版本中,我们不再需要手动去引入React
, plugin-syntax-jsx
已经向文件中提前注入了 _jsxRuntime api
。
例如我们这一段代码,经过babel转移后调用React的内部方法,将其转换成ReactElement
对象。
<div className="hcc">
123
<span>yx</span>
</div>
// 新版Automatic
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
/*#__PURE__*/_jsxs("div", {
className: "hcc",
children: ["123", /*#__PURE__*/_jsx("span", {
children: "yx"
})]
});
// 老版Classic
/*#__PURE__*/React.createElement("div", {
className: "hcc"
}, "123", /*#__PURE__*/React.createElement("span", null, "yx"));
主要是分为三部分:1. 对应的tag
字段, 2. 属性和children, 3. key
等一些特殊字段。
实现
在开发环境,babel会引入React中jsx-dev-rumtime.js
文件,通过调用里面的jsxDEV
方法,生成对应的ReactElement。所以我们需要实现一个对应的方法。
const ReactElement = function (
type: Type,
key: Key,
ref: Ref,
props: Props
): ReactElementType {
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref,
props,
__mark: "hcc",
};
return element;
};
export const jsxDEV = (type: ElementType, config: any) => {
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;
}
}
return ReactElement(type, key, ref, props);
};
主要是找到一些特殊的字段(key
和ref
等),然后传入ReactElement
中, 生成ReactElement。具体的打包流程可以查看代码仓库。