目标:在页面中呈现APP。
要求:我们写的mini-react api和react的api对齐。
学习方法:将大任务拆解为小任务,确保清晰的学习路径。
任务拆解:
V0.1
vdom 写死,dom 写死。
/**
* main.js vdom 写死,dom 写死;
*/
const element = document.createElement("div");
element.id = "app";
document.querySelector("#root").append(element);
const textElement = document.createTextNode("");
textElement.nodeValue = "app";
element.append(textElement);
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="main.js"></script>
</body>
</html>
OK, 页面出现app,成功的第一步,到下一个小任务。
V0.2
vdom动态生成,dom写死
/**
* main.js vdom 动态生成,dom 写死;
*/
// 数据结构设计 vdom 是一个 js object,思考里面有什么属性
const textEl = {
type: "TEXT_ELEMENT",
props: {
nodeValue: "app",
children: [],
},
};
const el = {
type: "div",
props: {
id: "app",
children: [textEl],
},
};
const element = document.createElement(el.type);
element.id = el.props.id;
document.querySelector("#root").append(element);
const textElement = document.createTextNode("");
textElement.nodeValue = textEl.props.nodeValue;
element.append(textElement);
写死的数据结构,进一步复用
const createElement = (type, props, ...children) => {
return {
type,
props: {
...props,
children,
},
};
};
const createElementText = (nodeValue) => {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue,
children: [],
},
};
};
const textEl = createElementText("app");
const App =createElement("div", { id: "app" }, textEl);
const element = document.createElement(App.type);
element.id = App.props.id;
document.querySelector("#root").append(element);
const textElement = document.createTextNode("");
textElement.nodeValue = textEl.props.nodeValue;
element.append(textElement);
OK,vdom动态生产完成,下一步dom动态生成,很有打怪升级的感觉。
V0.3
vdom动态生成,dom动态生成
const createElement = (type, props, ...children) => {
return {
type,
props: {
...props,
children,
},
};
};
const createElementText = (nodeValue) => {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue,
children: [],
},
};
};
const textEl = createElementText("app");
const App = createElement("div", { id: "app" }, textEl);
const render = (App, container) => {
const dom =
App.type === "TEXT_ELEMENT"
? document.createTextNode(App.type)
: document.createElement(App.type);
// 处理props
Object.keys(App.props).forEach((key) => {
if (key !== "children") {
dom[key] = App.props[key];
}
});
// 处理children
App.props.children?.forEach((child) => {
render(child, dom);
});
container.append(dom);
};
render(App, document.querySelector("#root"));
const textEl = createElementText("app"); const App = createElement("div", { id: "app" }, textEl); 这里还可以精简
const createElement = (type, props, ...children) => {
return {
type,
props: {
...props,
children: children.map((child) => {
return typeof child === "string" ? createElementText(child) : child;
}),
},
};
};
const createElementText = (nodeValue) => {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue,
children: [],
},
};
};
const App = createElement("div", { id: "app" }, "app");
const render = (App, container) => {
const dom =
App.type === "TEXT_ELEMENT"
? document.createTextNode(App.type)
: document.createElement(App.type);
// 处理props
Object.keys(App.props).forEach((key) => {
if (key !== "children") {
dom[key] = App.props[key];
}
});
// 处理children
App.props.children?.forEach((child) => {
render(child, dom);
});
container.append(dom);
};
render(App, document.querySelector("#root"));
好了,完成前面的三个小任务,我们还有一个要求,和react api对齐。 我们现在的代码结构是这样的:
我们的要求是这样的:
那就开始封装了。
// core/ReactDom.js
import React from "./React.js"; //引入需要注意要写全文件名
const ReactDom = {
createRoot(container) {
return {
render(App) {
React.render(App, container);
},
};
},
};
export default ReactDom;
// core/React.js
const render = (App, container) => {
const dom =
App.type === "TEXT_ELEMENT"
? document.createTextNode(App.type)
: document.createElement(App.type);
// 处理props
Object.keys(App.props).forEach((key) => {
if (key !== "children") {
dom[key] = App.props[key];
}
});
// 处理children 用到了递归
App.props.children?.forEach((child) => {
render(child, dom);
});
container.append(dom);
};
const createElement = (type, props, ...children) => {
return {
type,
props: {
...props,
children: children.map((child) => {
return typeof child === "string" ? createElementText(child) : child;
}),
},
};
};
const createElementText = (nodeValue) => {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue,
children: [],
},
};
};
const React = {
render,
createElement,
};
export { createElement };
export default React;
// App.js
import { createElement } from "./core/React.js";
const App = createElement("div", { id: "app" }, "app");
export default App;
// main.js
import ReactDom from "./core/ReactDom.js";
import App from "./App.js";
ReactDom.createRoot(document.querySelector("#root")).render(App);
好了,小目标完成。