type
status
date
slug
summary
tags
category
icon
password
Edited
Mar 20, 2023 11:37 AM
Created
Mar 20, 2023 04:58 AM
MReact (Mock React)
MReact目标是, 通过提供相同API的更简单实现, 以及如何构建它的逐步说明,使React的内部更容易理解. 一旦你理解了React如何在内部工作,使用它将更容易.模板:
ps:环境不是很好实现
JSX
JSX 实际上是一种语法糖,它本质上其实是 React.createElement(type, props, ...children) 的简易方式。例如:
实际上是:
Babel
为了使浏览器的理解,需要的代码由预处理转化为-有效的JS,像babel
babel从上面将JSX转换为:你也可以在线体验一些
Babel 的转换对于 { 变量 }
Babel会对该变量自动解析成一个新的节点
例如:
转换:
自定义配置React的createElement
/** @jsx MReact.createElement */ 自定义自己实现的createElement方法Step I: The createElement Function
createTextElement
Babel转换
根据我们自定义的
createElement方法,转换为Step II: The render Function
当前结构代码
index.js
mreact.js
Step III: Concurrent Mode
并发模式
问题
让我们注意一下这段代码(这个递归调用有问题)
一旦我们开始渲染,直到渲染完完整的元素树才会停止。如果元素树很大,它可能会阻塞主线程太久。如果浏览器需要做高优先级的事情,比如处理用户输入或保持动画流畅,它必须等到渲染完成。
比如处理用户输入或保持动画流畅,它将不得不等待渲染完成。
所以我们要把工作分解成小的单元,当我们完成每个单元后,如果还有其他需要做的事情,我们会让浏览器中断渲染。
接下来先认识一个
Web APIrequestIdleCallback
requestIdleCallback 会在帧结束时并且有空闲时间。或者用户不与网页交互时,执行回调- 回调接受一个参数
deadline,deadline是一个对象,对象上有两个属性
timeRemaining,timeRemaining属性是一个函数,函数的返回值表示当前空闲时间还剩下多少时间
didTimeout,didTimeout属性是一个布尔值,如果didTimeout是true
单元拆分执行实现
Step IV: Fibers
为了组织工作单元,我们需要一个数据结构:
fiber树我们将为每个元素提供一个
fiber,每个fiber将成为一个unit例子:
fiber树结构:
所需要做的
在渲染中,我们将创建
root fiber并将其设置为 nextUnitOfWork。剩下的工作将发生在 performUnitOfWork函数上,我们将为每个fiber做三件事:- 将元素添加到 DOM
- 为元素的子元素创建
fiber
- 选择下一个
unit
当我们完成对
fiber的工作时,如果它有一个子fiber,那么该纤程将成为下一个unit如果
fiber没有孩子,我们使用sibling作为下一个unit如果
fiber既没有孩子也没有sibling,我们就去找“叔叔”:父母的兄弟姐妹。此外,如果
parent没有sibling,我们会继续向上遍历parent,直到我们找到一个有sibling的parent或直到我们到达root。如果我们已经到达根目录,则意味着我们已经完成了此渲染的所有工作。实现
creatDOM
将之前
render函数创建dom的行为分离出来render
render现在标记下一次需要执行的工作performUnitOfWork
performUnitOfWork现在用于每一个元素的创建和创建fiber树的主要逻辑,并将fiber树拆分成一个个叶子节点为一个个unit工作单元来执行Step V: Render and Commit Phases
问题
每次处理一个元素时,我们都会向 DOM 添加一个新节点。请记住,浏览器可能会在我们完成整个树的渲染之前中断我们的工作。在这种情况下,用户会看到一个不完整的用户界面。我们不希望那样。
所以将节点绑定到页面的操作要换个地方执行
render
记录
fiber树commit
当fiber树构建完成后,在commitRoot中将所有的fiber添加到页面
即将整个fiber树提交给DOM
workLoop
Step VI: Reconciliation
问题
到目前为止,我们只向 DOM 添加内容,但是更新或删除节点呢?
这就是我们现在要做的,我们需要将我们在渲染函数上接收到的元素与我们提交给
DOM的最后一个 fiber树进行比较。因此,我们需要在完成提交后保存对“我们提交给
DOM 的最后一个fiber树”的引用。我们称之为 currentRoot。我们还为每个
fiber添加了alternate属性。此属性是指向旧fiber的链接,旧fiber是我们在前一个提交阶段提交给 DOM 的fiber添加
reconcileChildren
现在让我们从创建新纤程的
performUnitOfWork 中提取代码到一个新的 reconcileChildren 函数。比较
旧fiber和新元素,我们使用类型:- 如果
旧fiber和新的元素有相同的类型,我们可以保留 DOM 节点并用新的 props 更新它
- 如果类型不同并且有一个新元素,则意味着我们需要创建一个新的 DOM 节点
- 如果类型不同并且有
旧fiber,我们需要删除旧节点
还需要添加一个
deletions数组,用于记录要删除的fibercommit
updateDom
更改creatDom
当前结构代码
到目前为止,整体架构差不多完成,剩下的都是一些零碎的
index.js
mreact.js
Step VII: Function Components
问题
函数组件在两个方面有所不同:- 来自
函数组件的fiber没有DOM节点
children来自运行函数而不是直接从props中获取
updateHostComponent
将
performUnitOfWork中普通元素的逻辑提取出来处理performUnitOfWork
updateFunctionComponent
commit
修改
domParent的逻辑同理,修改删除的逻辑
commitDeletion
Step VIII: Hooks
典型例子:
前提条件
我们需要在调用函数组件之前初始化一些全局变量,以便我们可以在
useState 函数内部使用它们。首先我们设置正在进行的
wipFiber。我们还在 fiber 中添加了一个 hooks 数组,以支持在同一个组件中多次调用 useState。我们会跟踪当前的挂钩索引。
useState
就这样。我们构建了自己的 React 版本。
完整代码
index.js
mreact.js
参考链接
- 作者:JinSo
- 链接:https://jinso.top/article/framework-mini-react
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。





.webp?table=block&id=106cee95-0faf-8024-b4d9-cef5935e9f79&t=106cee95-0faf-8024-b4d9-cef5935e9f79&width=800&cache=v2)










