手摸手实现超简易版mini-react(1)

本系列内容包含

  • 使用vite创建项目(解析jsx
  • 实现jsx挂载节点,运行起来项目
  • 实现vdom
  • 使用requestIdleCallback实现闲时加载
  • dom更新(增删改)
  • 支持FunctionComponent
  • 实现useState
  • 实现useEffect

本文为第1章节,实现前4条


创建项目

1. 使用vite创建项目:

npm create vite@latest

然后将这个项目跑起来:

2.实现jsx挂载节点,运行起来项目

众所周知, react项目使用的是jsx语法,我们使用vite构建的目的就是为了解析jsx文件(当然其他工具也可以)

而react项目初始化时main.jsx中的操作大致是:

我们先来实现前半部分,也就是:

ReactDom.createRoot(document.getElementById('app'))

在根目录中创建core目录,然后创建ReactDom文件:

创建createRoot方法,然后export

梳理一下目前可以想到的createRoot功能: 1.接受一个dom, 作为挂载的根节点 2.返回一个obejct, obejct中含有render方法

我们先忽略其他,实现给根节点挂载一个元素:

然后ReactDom.js中打印接收到的值:

切回浏览器,查看项目运行情况

发现报了一个React is not defind

这是因为vite解析jsx时默认使用react解析,

既然没有React,那我们就在main.jsx中创建一个React对象,看他怎么说:

发现又报了一个新的错误:

这次不是Reactundefind了,而是React.createElement is not a function

那我们就给React对象加一个createElemnt方法,并打印接收到的参数:

然后控制台查看打印:

可以看到,这是我们render函数中传入dom的描述,其中第一个参数是元素类型,第二个参数是元素的props,第三个参数是子节点,再结合他的名字createElement,我们不难猜出,这个方法是根据jsxdom的描述创建一个dom对象

既然如此,我们就实现这个简单功能:

处理完毕,我们返回控制台发现这次并没有报错,而且ReactDom中的render函数打印到了值,这下我们终于可以将dom挂载到根节点了!

返回页面,渲染也正常:

但这时候就有问题了,我们总不能只挂载一个元素吧?如果挂载很多我们还能支持吗?

我们修改一下dom结构:

回到浏览器,发现渲染了一些奇怪的东西:

那看来是React.createElemnt创建dom有问题,回到React.createElement打印:

发现函数被执行了3次,每次分别打印为:

我们发现接收到的children不对呀,明明div id=app里有两个子元素,怎么只收到一个呢?这是因为createElement参数中children并不是以数组的方式传入,而是从第三个参数开始每个子元素传入一个参数,所以我们可以这样接收:

现在我们再打印,接受到的children就正常了:

那我们我们再更改一下我们创建子节点的方法,让他支持多级:

这样浏览器就渲染正常:

我们可以把React提取到/core/React.js文件中

这样我们就实现了 ReactDom.createRoot(document.getElementById('app'))

3.实现vdom

但是还有一个问题:react使用的是vdom(Virtual DOM),根据vdom合适的时间进行渲染,我们现在要考虑如何实现它:

首先是vdomvdom就是一个可以描述dom节点的object对象,react使用链表的方式表示各个节点的关系。

我们先将React.createElement创建dom改为创建vdom:

需要注意的是children字符串时,创建对应的TextNode。

那么如何将vdom转化为链表呢?

这时ReactDom.redner会报错:

这是因为他接收的dom是我们的vdom描述了。

我们在ReactDom.render中进行递归生成dom并挂载:

这样页面渲染正常:

4.实现闲时加载 requestIdleCallback API

使用[requestIdleCallback](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback)

简单放个描述:window.requestIdleCallback() 方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序

先简单使用一下这个api:

这样控制台就会在空闲时打印count

我们将ReactDom.Render放到React.js中,ReactDom引入React并使用React.render,这样我们的逻辑都放到React.js文件中

之后我们将vdom转换为链表结构,使用child字段表示第一个子元素,使用sibling表示第一个兄弟元素, 使用parent元素表示父元素。这样方便我们使用requestIdleCallback每次只调用一个元素。

我们当前的vdom结构为:

转化为:

root -> foot -> text -> bar -> text

我们把方法命名为perWorkOfUnit, perWorkOfUnit先实现render方法的创建dom,赋值props:

给元素转换链表:

这样我们就可以生命一个全局变量nextUnitOfWork,在render中将要处理的元素赋值到nextUnitOfWork, 然后使用requestIdleCallback进行闲时处理:

回到页面,发现最外层的app节点已经挂载了,而他的子元素并没有挂载:

这是因为我们render中只传入了app节点,我们需要在每次perWorkOfUnit执行了后需要将nextUnitOfWork赋值 给当前节点的child或者sibling,这样才会往下进行:

再次回到页面,发现这次foo节点渲染了,bar节点没有渲染:

哪里出了问题?

通过debugger可以看到,函数在foo下的text执行完后就停止了,显示fiberchildsibling都为空。

再看图:

不难发现,我们foo/textsibling确实是空的,这时我们应该执行的是foosibling也就是textparent节点的sibling,我们可以通过一个while循环处理:

这样页面就渲染正常了:

这样我们的闲时加载就完成了!

仓库地址:github.com/mmmmr/mini-... 这个是全的


下节预告:实现FunctionComponent与修改(新增删除修改)


@崔佬的mini-react 游戏副本

相关推荐
牛仔只喝牛奶19 小时前
多端跨端技术选型:小程序 + H5 + App 的两栈拆分落地
react native·react.js
wear工程师1 天前
setState 到底是不是异步的?这题从 React 16 问到 18,标准答案早就变了
react.js·面试
wanghowie1 天前
LangGraph4j 落地(二)— ReAct 多步工具与 trace 扩展
前端·react.js·前端框架
csj501 天前
前端基础之《React(11)—state声明式》
前端·react.js
张元清1 天前
React useLocalStorage Hook:SSR 安全的持久化状态(2026)
javascript·react.js
析数塔1 天前
2026前端框架深度解析:React Compiler、Angular Zoneless、Vue 3.5重写游戏规则
前端·vue.js·react.js
杖雍皓1 天前
没有服务器如何构建现代 Web 应用:平台选型与组合策略
运维·服务器·前端·react.js·github·netlify·vercel
sun_weitao1 天前
微前端框架之qiankun 怎么用?vue3+vite+qiankun 和 react + qiankun
前端·react.js·前端框架
名字还没想好☜2 天前
React useEffect 依赖数组踩坑:闭包陷阱与清理函数
前端·javascript·react.js·react·useeffect
我是大卫2 天前
【图】React源码解析-第四部分:事件与跨层传递
react.js·源码