本节目标:在页面中呈现 app(使用React的API)
效果如下:
js
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
ReactDoM.createRoot(document.getElementById( "root")).render(<App >);
实现思路大致为以下五步:
- 动态创建虚拟dom (注意虚拟dom的形式,要符合jsx的规范)
- 创建渲染函数
- 重构API
- 使用jsx代替掉js写法
- 借助vite实现jsx的解析
创建虚拟dom
虚拟dom是一个对象,是对真实dom的描述,形式如下:
js
//div节点
const divElement = {
type:'div',
props:{
id:'app',
children:[]
}
}
对于文本节点,我们需要用到document.createTextNode
这个API去创建,所以要和其它节点做一个区分,故我们将其type
固定,便于区分
js
const textElement = {
type:'TEXT_ELEMENT',
props:{
nodeValue: 'hello world'
children:[]
}
}
知道了虚拟dom的形式,我们就可以开始编写函数去动态地创建虚拟dom了:
js
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
//如果子节点是文本, 调用创建节点的函数处理一下
children: children.map((child) =>
typeof child === "string" ? createTextElement(child) : child
),
},
};
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
实现render函数
有了虚拟dom,我们就可以将其转换为真实dom,并插入到父容器中,我们通过render函数来实现这一点,可以分为三部分:
- 虚拟dom--->真实dom
- 添加props
- 递归处理children
js
function render(container, el) {
//虚拟dom--->真实dom
const dom =
el.type === "TEXT_ELEMENT"
? document.createTextNode(el.nodeValue)
: document.createElement(el.type);
//添加props
Object.keys(el.props).forEach((key) => {
if (key !== "children") {
dom[key] = el.props[key];
}
});
//递归处理children
const children = el.props.children;
children.forEach((child) => {
render(dom, child);
});
container.append(dom);
}
重构API
核心代码已实现,我们可以将其封装一下,重构为React提供的API的形式:
js
//React.js (函数具体实现见上文)
export default {
createElement,
render,
};
js
//ReactDOM.js
import React from "./React.js";
const ReactDOM = {
createRoot(container) {
return {
render(el) {
React.render(container, el);
},
};
},
};
export default ReactDOM;
接下来我们在main.js中引入测试一下:
js
import ReactDOM from "./core/ReactDOM.js";
import React from "./core/React.js";
const App = React.createElement("div", { id: "app" }, "hello", "-", "world");
ReactDOM.createRoot(document.querySelector("#root")).render(App);
效果如下,符合我们的预期:
现在我们需要解决的是对 jsx 的处理,解析jsx的工具有很多,如 Webpack、Vite、Babel 等。这里我们选择 Vite 来解析 jsx。
借助 Vite 实现 JSX 的解析
Vite 解析 jsx 的时候,会调用React.createElement
函数。这也是为什么我们平时在项目中并没有使用React却需要引入的原因。
我们可以打印一下看看效果:
jsx
function Test() {
return <div id="test">test</div>;
}
console.log(Test);
输出如下:
我们将App改写为jsx的形式即可,注意要正确引入编写的 React.js 和 ReactDOM.js
jsx
import React from "./core/React.js";
const App = <div id="app">hello world</div>;
export default App;