当你想要快速掌握React开发技能却又感到困惑时,七天学会mini-react将成为你的最佳选择!这款全新的学习工具不仅简洁易懂,还能帮助你在短短七天内掌握React的精髓。无需繁琐的教程,无需枯燥的学习过程,只需七天,你就能成为React开发的高手!赶快加入我们,一起探索无限可能吧!#学习React
#快速掌握技能
#七天挑战
#mini-react
1、七天搞定mini-react
第一天: 实现最简 mini-react
实现最简 mini-react
首先我们试着实现一下render
函数,在这之前我们可以先看看是它是如何渲染到页面上的,如果要是我们会怎么做?
我们先来创建两个文件
// 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>
</body>
<script src="./main.js" type="module"></script>
</html>
// main.js
const dom = document.createElement("div")
dom.id = "app"
document.querySelector("#root").append(dom)
const textNode = document.createTextNode("")
textNode.nodeValue = "app"
dom.appendChild(textNode)
我们创建了一个id
为root
的div
,我们需要在root
里面创建一个id
为app
的div
,我们通过原生方法去创建,并且我们还创建了一个textNode
节点,并赋值为app
,最后把文本节点放在了新创建的div
中,这样就简单的完成了app
的挂载
接下来我们对代码进行优化,我们使用对象来模拟节点,相当于虚拟DOM的方式,文本类型就是TEXT_ELEMENT
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(textNod
接下来我们继续对代码进行优化,发现很多代码都是在创建元素,接下来我们来实现具体方法,我们创建两个方法,专门用来创建普通元素,以及文本元素
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => {
return typeof child === "string" ? createTextNode(child) : child
}),
},
}
}
function createTextNode(text, ...children) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
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)
通过运行,发现在处理普通元素的时候,需要对children
进行处理,如果内容为文本元素,需要使用createTextNode
方法,修改完之后,页面正确显示app
接下来我们就可以来实现render
方法啦!
这里在处理el.props
和el.children
时,需要分开处理,使用递归的方式,就可以实现render
啦
function render(el, container) {
const dom = el.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(el.type)
// 设置id和class
Object.keys(el.props).forEach(key => {
if (key !== "children") {
// 给DOM创建props
dom[key] = el.props[key]
}
})
const children = el.props.children
children.forEach(child => {
render(child, dom)
})
container.append(dom)
}
const textEl = createTextNode("app")
// const App = createElement("div", { id: "app" }, textEl)
const App = createElement("div", { id: "app" }, "hi-", "mini-react")
render(App, document.querySelector("#root"))
我们可以修改children
的内容,发现运行十分成功,非常完美!
我们打印下app
的内容,发现跟我们的虚拟dom
一模一样
接下来我们来实现这个吧
const ReactDOM = {
createRoot(container) {
return {
render(el) {
render(el, container)
},
}
},
}
ReactDOM.createRoot(document.querySelector("#root")).render(App)
通过运行,我们发现没有问题,这样我们就已经实现了
接下来我们对代码进行抽离
将代码进行抽离
// React.js
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => {
return typeof child === "string" ? createTextNode(child) : child
}),
},
}
}
function createTextNode(text, ...children) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children,
},
}
}
function render(el, container) {
const dom = el.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(el.type)
// 设置id和class
Object.keys(el.props).forEach(key => {
if (key !== "children") {
// 给DOM创建props
dom[key] = el.props[key]
}
})
const children = el.props.children
children.forEach(child => {
render(child, dom)
})
container.append(dom)
}
const React = {
render,
createElement,
}
export default React
// ReactDom.js
import React from "./React.js"
const ReactDOM = {
createRoot(container) {
return {
render(el) {
React.render(el, container)
},
}
},
}
export default ReactDOM
// App.js
import React from "./core/React.js"
const App = React.createElement("div", { id: "app" }, "hi-", "mini-react")
export default App
// index.js
import ReactDOM from "./core/ReactDom.js"
import App from "./App.js"
ReactDOM.createRoot(document.querySelector("#root")).render(App)
这样我们就已经完成了简单的mini-react
,但是,我们毕竟是用js
来实现的,但是一般我们是使用jsx
啊,那怎么办呢?后面我们就来实现jsx
的版本
使用 jsx
这里我们想使用JSX
的话,我们需要借助一些库,例如webpack、bable、vite
都行,这里的话,我们采用vite
去实现
首页安装一下vite
pnpm create vite
这里选择第一个就行
// App.jsx
import React from "./core/React.js"
const App = React.createElement("div", { id: "app" }, "hi-", "mini-react")
export default App
// main.js
import ReactDOM from "./core/ReactDom.js"
import App from "./App.jsx"
ReactDOM.createRoot(document.querySelector("#root")).render(App)
我们把上面的代码放进去,并且修改一下main.js
的内容,并且需要修改一下index.html
中的id
等于root
然后运行,发现可以运行了
import React from "./core/React.js"
// const App = React.createElement("div", { id: "app" }, "hi-", "mini-react")
const App = <div id="app">hi-mini-react</div>
console.log(App)
export default App
我们在App.jsx
中去修改发现,依然可以运行,原因是因为我们导入了React
,它会自动解析
但是我们在main.js
中去修改的话,报错了
import ReactDOM from "./core/ReactDom.js"
import App from "./App.jsx"
ReactDOM.createRoot(document.querySelector("#root")).render(<App></App>)
后面我们使用function component
也还是不行,最终原因是因为我们还没有实现,但是基本的我们通过vite
去跑jsx
是没有问题的。
扩展 – 使用 vitest
做单元测试
首先我们需要安装一下vitest
pnpm i vitest -D
然后我们改一下package.json
{
"scripts": {
"test": "vitest"
},
"devDependencies": {
"vitest": "^1.4.0"
}
}
我们添加一个新的文件
// test/creatElement.spec.js
import React from "../core/React.js"
import { expect, describe, it } from "vitest"
describe("createElement", () => {
it("props is null", () => {
const el = React.createElement("div", null, "hi")
expect(el).toMatchInlineSnapshot(`
{
"props": {
"children": [
{
"props": {
"children": [],
"nodeValue": "hi",
},
"type": "TEXT_ELEMENT",
},
],
},
"type": "div",
}
`)
})
it("should return element vdom", () => {
const el = React.createElement("div", { id: "root" }, "hi")
expect(el).toMatchInlineSnapshot(`
{
"props": {
"children": [
{
"props": {
"children": [],
"nodeValue": "hi",
},
"type": "TEXT_ELEMENT",
},
],
"id": "root",
},
"type": "div",
}
`)
})
})
然后我们通过pnpm test
去运行
运行成功,以后我们就可以添加测试了
扩展 – 自定义 react
的名字
非常简单,只需要加一个注释语法即可
/**@jsx CReact.createElement */
import CReact from "./core/React.js"
// const App = React.createElement("div", { id: "app" }, "hi-", "mini-react")
const App = <div id="app">hi-mini-react</div>
export default App
原文链接:https://juejin.cn/post/7350231743590891520 作者:旧时光_