目标:使用和react一样的api在页面渲染字符
先看下react的Api写法
- main.jsx
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
ReactDOM.createRoot(document.querySelector("#root")).render(<App />);
- App.jsx
function App() {
return <div>app</div>
}
export default App;
开始我们的写法
-
从最简单的版本开始
- 新建my-react项目文件夹
-
新建index.html
...
<body>
<div id="root"></div>
<script type="module" src="./main.js"></script>
</body>
...
- main.js
console.log('fuck')
- 使用http-server运行起来确保能打印成功
开始写main.js,实现最简单的app展示
- 第一版main.js
// 1. 创建 div 元素节点
const dom = document.createElement('div');
// 创建了一个 <div> HTML 元素(此时尚未添加到文档中)
// 此时 dom 的内存结构:<div></div>
// 2. 设置元素 ID
dom.id = "app";
// 现在 dom 变为:<div id="app"></div>
// 3. 将元素添加到 DOM 树
document.querySelector('#root').append(dom);
// 在文档中查找 id="root" 的元素,将新建的 div 作为子元素追加进去
// 假设页面存在 <div id="root"></div>,操作后结构变为:
// <div id="root">
// <div id="app"></div>
// </div>
// 4. 创建空的文本节点
const textNode = document.createTextNode("");
// 创建了一个空的文本节点(此时内容为空字符串)
// 5. 设置文本内容
textNode.nodeValue = "app";
// 将文本节点的值设置为 "app"
// 此时 textNode 的内容是 "app"
// 6. 添加文本到元素
dom.appendChild(textNode);
// 将文本节点追加到 id="app" 的 div 中
// 最终 DOM 结构:
// <div id="root">
// <div id="app">app</div>
// </div>
- 第二个版本,进一步使用v-dom优化main.js创建app
// 注意:type props children属性
const textEl = {
type: "TEXT_ELEMENT",
props: {
nodeValue: "app",
children: []
}
}
const el = {
type: "div",
props: {
id: "app",
children: [
textEl
]
}
}
const dom = document.createElement(el.type);
dom.id = el.props.id;
document.querySelector('#root').append(dom);
const textNode = document.createTextNode("");
textNode.nodeValue = textEl.props.nodeValue;
dom.appendChild(textNode)
继续完善实现动态创建
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 App = createElement("div", { id: "app" }, textEl);
const dom = document.createElement(App.type);
dom.id = App.props.id;
document.querySelector('#root').append(dom);
const textNode = document.createTextNode("");
textNode.nodeValue = textEl.props.nodeValue;
dom.appendChild(textNode)
继续优化代码,编写render函数
// const dom = document.createElement(App.type);
// dom.id = App.props.id;
// document.querySelector('#root').append(dom);
// const textNode = document.createTextNode("");
// textNode.nodeValue = textEl.props.nodeValue;
// dom.appendChild(textNode)
// 将以上两段代码优化成render函数
function render(el, container) {
// 1. 判断元素类型是文本还是其他类型, 如果是文本类型则创建文本节点,否则创建元素节点
const dom = el.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(el.type);
// 2.设置元素的属性值为 el.props 中的属性
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);
});
// 3. 将创建的元素节点添加到指定的容器中
container.appendChild(dom);
}
const textEl = createTextNode("app")
const App = createElement("div", { id: "app" }, textEl);
render(App, document.querySelector("#root"));
继续优化直接给app,而不是textNode
// 实现以下效果
const App = createElement("div", { id: "app" }, 'app1', 'app2');
// 通过修改children实现
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => {return typeof child === "string"? createTextNode(child) : child})
}
}
}
整合api成react对应api
const ReactDOM = {
createRoot(container) {
return {
render(App) {
render(App, container);
}
}
}
}
const App = createElement("div", { id: "app" }, 'aaa->', 'bbbb');
ReactDOM.createRoot(document.querySelector("#root")).render(App)
重构下代码分离业务代码和自己写的mini-react框架代码
- core/react.js
function createTextNode(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: []
}
}
}
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => {return typeof child === "string"? createTextNode(child) : child})
}
}
}
function render(el, container) {
// 1. 判断元素类型是文本还是其他类型, 如果是文本类型则创建文本节点,否则创建元素节点
const dom = el.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(el.type);
// 2.设置元素的属性值为 el.props 中的属性
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);
});
// 3. 将创建的元素节点添加到指定的容器中
container.appendChild(dom);
}
const textEl = createTextNode("app")
const React = {
render,
createElement,
}
export default React
- core/ReactDom.js
import React from './React.js';
const ReactDOM = {
createRoot(container) {
return {
render(App) {
React.render(App, container);
}
}
}
}
export default ReactDOM
- App.js
import React from './core/React.js';
const App = React.createElement("div", { id: "app" }, 'aaa->', 'bbbb');
- main.js
import ReactDOM from './core/ReactDom.js'
import App from './App.js'
ReactDOM.createRoot(document.querySelector("#root")).render(App)
遗留问题
- 如何使用JSX