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 API
requestIdleCallback
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://jinso365.top/article/framework-mini-react
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。