第一节:实现最简单 mini-react
本节课使用js创建一个div元素,通过改造创建div元素属性、内容的方式来体验现在主流前端框架的核心思想,
虚拟dom
(vdom)
使用js创建元素
很简单的实现了:
const app = document.createElement("div");
app.id = "app";
document.querySelector("#root").append(app);
const textNode = document.createTextNode("");
textNode.nodeValue = "app";
app.append(textNode);
然后我们开始改造。
改造1. 使用对象来描述元素 el
const textEl = {
type: "TEXT_ELEMENT",
props: {
nodeValue: "app",
children: [],
},
};
const el = {
type: "div",
props: {
id: "app",
children: [textEl],
},
};
然后使用对象中的属性代替直接赋值
const app = document.createElement(el.type);
app.id = el.props.id;
document.querySelector("#root").append(app);
const textNode = document.createTextNode("");
textNode.nodeValue = textEl.props.nodeValue;
app.append(textNode);
改造2. 使用函数创建元素和创建文字节点
把创建textEl
和el
封装成函数方便调用
function createTextNode(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children,
},
};
}
const textEl = createTextNode("app");
const el = createElement("div", { id: "app" }, textEl);
页面正常显示 app。 createElement
函数是 mini-react 的核心。它接收元素的类型
、属性
和子元素
作为参数。对于子元素,若它们是字符串或数字,函数会通过 createTextNode
创建一个文本类型的虚拟 DOM 节点;若是其他类型,则直接作为子节点。
改造3. 创建 render 函数来统一处理
function render(el, container) {
// 根据不同类型创建不同的dom
const dom =
el.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(el.type);
// 添加属性
Object.keys(el.props).forEach((key) => {
if (key !== "children") {
dom[key] = el.props[key];
}
});
// 处理子元素
const children = el.props.children;
children.forEach((child) => {
render(child, dom);
});
// 添加到容器中
container.append(dom);
}
// 使用render
const App = createElement("div", { id: "app" }, "app", "---hi mini-react");
console.log(App);
render(App, document.querySelector("#root"));
到此我们使用对象来描述创建dom的过程
改造4. 对齐 React 的 api 构建核心方法
分离成两个核心文件React.js和ReactDom.js
打开一个最简单的react项目,我们知道React导出的有一个render
方法,还有React.dom的createRoot
方法,我们据此实现如下:
React.js
// core/React.js
// 实现部分省略
const React = {
render,
}
export default React
ReactDom.js
// core/ReactDom.js
import React from './React.js'
const ReactDOM = {
createRoot(container) {
return {
render(el) {
React.render(el, container)
}
}
}
}
export default ReactDOM
改造5. 引入 jsx
JSX 的本质是提供一种更接近 HTML 的语法来描述界面,它通过 createElement
函数转换为 JavaScript 对象。这种转换把组件的每个部分——包括类型、属性和子元素——都转换成了可以被 JavaScript 处理的格式。
// 引入jsx前
const app = React.createElement('div', { id: 'app' }, 'app')
// 引入jsx后
const app = <div id="app">app</div>
使用vite
创建一个空项目,然后改造js为jsx文件,运行项目我们会发现报错了:
Uncaught ReferenceError: React is not defined
这是因为对于拓展名为.jsx/.tsx
的文件,vite会默认使用React.createElement
转化成vdom
,但是我们React.js
文件没有引入且没有导出createElement
方法。修改core/React.js
导出方法,然后在使用jsx
语法的文件中引入react
对象后,项目正常运行。
第一节完。