调试准备
调试项目使用 vite 搭建,进行 react 开发环境调试,所以使用的所有 react 方法都是 dev 方法。
vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
export default defineConfig({
plugins: [react()],
resolve: {
// 配置别名,导入 react 时从本地代码导入。
alias: {
react: path.resolve(__dirname, "./src/react"),
shared: path.resolve(__dirname, "./src/shared"),
"react-dom": path.resolve(__dirname, "./src/react-dom"),
scheduler: path.resolve(__dirname, "./src/scheduler"),
"react-reconciler": path.resolve(__dirname, "./src/react-reconciler"),
},
},
});
jsconfig.json
{
"compilerOptions": {
"baseUrl": "./",
// 配置路径
"paths": {
"react/*": ["src/react/*"],
"react-dom/*": ["src/react-dom/*"],
"scheduler/*": ["src/scheduler/*"],
"shared/*": ["src/shared/*"],
"react-reconciler/*": ["src/react-reconciler/*"]
}
},
"exclude": ["node_modules", "dist"]
}
jsx 执行流程
graph LR
A["用户编写 JSX"] --> B["Babel 编译"]
B --> C["执行 React 运行时"]
babel 编译结果
js
// jsx 代码
const element = (
<h1>
hello <span>test</span> children
</h1>
);
// 编译结果 删除静态标记
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
const element = _jsxDEV("h1", {
children: [
"hello ",
_jsxDEV("span", { children: "test" }, undefined, false, { fileName: "example.jsx", lineNumber: 1, columnNumber: 15 }, this),
" children"
]
}, undefined, true, { fileName: "example.jsx", lineNumber: 1, columnNumber: 1 }, this);
由编译结果代码可知,babel 编译完成后,会自动从react/jsx-dev-runtime
中获取 jsxDEV 函数。之前配置调试环境时,将react
文件夹指向 src/react
,所以需要实现 src/react
。
实现
首先实现 jsx-dev-runtime
文件,这个文件在源码中功能很简单,只是导出函数。
jsx-dev-runtime.js
export { jsxDEV } from "./src/jsx/ReactJSXElement";
实现 jsxDEV 函数
jsxDEV
函数的功能很明确,根据参数生成虚拟 DOM 并返回。
由编译后代码可知 jsxDEV
函数接受两个参数。
- type 组件名(h1 div)
- config 属性
js
import hasOwnProperty from "shared/hasOwnProperty";
import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";
// 保留属性,不加入 props
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};
function hasValidKey(config) {
return config.key !== undefined;
}
function hasValidRef(config) {
return config.ref !== undefined;
}
function ReactElement(type, key, ref, props) {
// 虚拟DOM
return {
$$typeof: REACT_ELEMENT_TYPE,
type, // 组件名称 h1 span
key, // key
ref, // ref
props, // 属性对象 children id style
};
}
export function jsxDEV(type, config) {
let propName; // 属性名
let props = {}; // 属性对象
let key = null; // 唯一标识
let ref = null; // 引用,对应真实DOM
// 传入的配置中 key 值有效才赋值给 key
if (hasValidKey(config)) {
key = config.key;
}
// 传入的配置中的 ref 有效才赋值给 ref
if (hasValidRef(config)) {
ref = config.ref;
}
// 遍历传入配置中的字段,如果字段是 config 本身的属性
// 且字段不为保留属性,则赋值给 props
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
// 根据处理后的数据,生成虚拟 DOM
return ReactElement(type, key, ref, props);
}
shared/hasOwnProperty
const { hasOwnProperty } = Object.prototype;
export default hasOwnProperty;
shared/ReactSymbols
export const REACT_ELEMENT_TYPE = Symbol.for("react.element");
结束
以上就是 react 中的 jsx 处理。