为什么学习mini-react要从在页面上渲染app开始? 我本人使用react也有三年时间了,找过很多课程资料试图学习React源码,但不是很难看懂就是看完就忘,对一个很高级的创造规则的技术非要强制将它的知识体系硬生生的塞进大脑,我尝试了很多次,结果都很不理想^_^,我一度怀疑是我脑子有问题🤣,直到看到崔胡子叔叔的课程,才恍然大悟、茅塞顿开。
无论是Vue, React,还是angular,对于前端来说,从最最直观的视觉角度来说就是在页面如何呈现内的,所以mini-react就是从最基础的创建dom,渲染dom这种简单的方式,渐进式的去学习React源码。非常适合初学者学习React源码。
实现思路:
React 在页面中呈现App

在页面上呈现一个App,代码大体如下:
HTML
<div id="app">
App
</div>
第一阶段
markdown
1. 创建一个dom, id为app的div
2. 在dom里面创建一个文本节点
3. 渲染dom
HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
第一阶段实现方式:用非常简单的方式实现在页面呈现app
JavaScript
const dom = document.createElement("div");
dom.id = "app";
document.querySelector("#root").append(dom);
const textNode = document.createTextNode("");
textNode.nodeValue = "app";
dom.append(textNode);
第二阶段
这个阶段我们要使用react当中的一些思想来重构以上代码。
- 首先聊聊dom节点,很多情况下它是有层级的,比如:
HTML
<div id="app">
<div>
<div>
<div>
<div>
App
</div>
</div>
</div>
</div>
</div>
甚至有很多层,这个在我们创建dom阶段是个未知数,我们不确定它有多少层,而且它是树状结构,有层级的。
- 还有需要考虑到的React当中有虚拟DOM , 这里简称VDom 【Virtual DOM】,其数据结构就是一个Object。这对象需要哪些属性呢?结合第一阶段的实现需要有类型区分type , 分析是树状结构有层级之分children, 每种类型的节点有自己的属性props
所以结合第一阶段的实现思路:
- 创建一个对象element,数据结构是一个对象, 包含type, children, props
- 只需要将之前的写死的数据替换成对象中的对应的值即可。
JavaScript
const textElement = {
type:"text_element",
props:{
nodeValue:"app",
children:[]
}
}
const element = {
type:"div",
props:{
id:"app",
children:[textElement]
}
}
const dom = document.createElement(element.type);
dom.id = element.props.id;
document.querySelector("#root").append(dom);
const textNode = document.createTextNode("");
textNode.nodeValue = textElement.props.nodeValue;
dom.append(textNode);
第三阶段
到这里其实已经很明显,以上我们创建的节点即便是引入和VDom的概念,但很显然还是死值,所以这一阶段我们的重点是,***如何动态创建VDOM?***在这个阶段其实也比较简单,我们使用函数的方式,通过传参来完成节点的创建。
JavaScript
const createTextNodeElement = (text) => {
return {
type: "text_element",
props: {
nodeValue: text,
children: []
}
}
}
const createElement = (type,props,...children) => {
return {
type: type,
props: {
...props,
children
}
}
}
const textNodeElement = createTextNodeElement("app");
const App = createElement(
"div",
{id:"app"},
textNodeElement
)
const dom = document.createElement(App.type);
dom.id = App.id;
document.querySelector("#root").append(dom);
const textNode = document.createTextNode("");
textNode.nodeValue = textNodeElement.nodeValue;
dom.append(textNode);
第四阶段
第三阶段实现了动态创建VDOM,然后去渲染;但是很明显无论是createElement,还是createTextNodeElement,我们最终生成的dom结构是比较单一的,我们之前基于VDOM的特点:树状结构,有层级,加入了对应的属性,但是远远不够,VDOM的结构才是重点,它不可能是我们demo的那么简单,所以我们还需要动态的来生成未知层级的结构,很显然需要用到递归的思想。
一旦结构变的更复杂,怎么创建节点呢?首先思考我们说结构复杂,那肯定是复杂在children这个属性上了,它可能会有很多层,所以会如何创建节点 createElement ?如何渲染 render?实现思路如下:
JavaScript
const createTextNodeElement = (text) => {
return {
type: "text_element",
props: {
nodeValue: text,
children: []
}
}
}
const createElement = (type, props, ...children) => {
return {
type: type,
props: {
...props,
// 这里使用递归,在循环中去判断该创建文本节点,还是继续创建子节点
children: children.map((child) => {
return typeof child === "string" ? createTextNodeElement(child) : child
})
}
}
}
const textNodeElement = createTextNodeElement("app");
// 创建一个多层级的节点
const App = createElement(
"div",
{id:"app"},
"create app,",
"by holin,",
"welcome to mini-react"
)
function render(elment, container){
const dom = el.type === "text_element" ? document.createTextNode("")
: document.createElement(element.type);
// 设置属性
Object.keys(element.props).forEach((key) => {
if(key !== "children"){
dom[key] = element.props[key]
}
})
// 存在子孙结点,递归思想
const children = element.props.children;
if(!children){
children.forEach((child) => {
render(child,dom)
})
}
container.append(dom)
}
render(App,document.querySelector("#root"));
第五阶段
模仿React的文件结构,优化和重构我们的代码。