基于ReactV16.13.1
架构,从零实现React 🎉🎉🎉
👋👋👋 文章有任何不清楚的地方,欢迎给我提PR
我假设React是你日常开发的框架,在日复一日的开发中,你萌生了学习React源码的念头,在网上搜各种源码解析后,你发现这些教程可以分为2类:
-
《xx行代码带你实现迷你React》,《xx行代码实现React hook》这样短小精干的文章。如果你只是想花一点点时间了解下React的工作原理,我向你推荐 这篇文章,非常精彩。同时,这个仓库可能不适合你,因为他会花掉你很多时间。
-
《React Fiber原理》,《React expirationTime原理》这样摘录React源码讲解的文章。如果你想学习React源码,当你都不知道
Fiber
是什么,不知道expirationTime
对于React的意义时,这样的文章会给人“你讲解的代码我看懂了,但这些代码的作用是什么”的感觉。
这个仓库的存在就是为了解决这个问题。
简单来说,这个仓库有对应的一系列文章,文章会讲解React为什么要这么做,以及大体怎么做,但不会有大段的代码告诉你怎么做。
当你看完文章知道我们要做什么后,再来看仓库中具体的代码实现。
同时为了防止堆砌过多功能后,代码量太大影响你理解某个功能的实现,我为每个功能的实现打上一个git tag
。
通过切换git tag
浏览不同完成度的项目,执行npm start
启动该版本下的Demo
v6实现了React的异步调度器Scheduler(也就是说我们实现了requestIdleCallback
polyfill),并使用Scheduler
实现了异步render,也就是React ConcurrentMode。
之前的版本中,我们都是同步执行render流程。在v6中,我们会为产生的update
赋予一个优先级,高优先级的update
会优先进入render流程。甚至当低优先级的update
在render过程中我们触发了高优先级update
,这时会搁置低优先级render转而处理高优先级render,这很酷,不是么😄
相对应的,v6相对v5增加了大量代码和一些全局变量。不过没关系,我会在之后的文章介绍这一切是如何做到的。新增功能如下:
- Scheduler模块
- fiber的优先级冒泡机制
- ConcurrentMode
这真是React内部最复杂的机制了,让人头秃👨🦲
在v3中我们实现了状态更新,直接在FunctionComponent
函数体内触发更新会造成死循环,所以我们用计时器来触发。在业务中,我们一般是通过:
- 回调函数(ex:onClick)
useEffect hook
ClassComponent
生命周期钩子
来触发。既然我们已经实现了useState hook
,这一版我们就实现useEffect hook
,新增功能如下:
-
useEffect hook
首屏及再次渲染的完整逻辑
之前只能更新单一节点,这次实现了大名鼎鼎的React Diff算法,可以更新多个兄弟子节点了😄,新增功能如下:
- 节点支持
key
prop -
commit
流程支持Deletion effectTag
处理 -
reconcileChildrenArray
支持非首次渲染的diff算法
ps:支持Deletion effectTag
处理是为了应对:
// 首屏渲染的组件
[a, b, c]
// 再次渲染的组件
[a, null, c]
在这种情况下b fiber被标记为Deletion effectTag
,对应的DOM节点需要删除
之前的版本只实现了首屏渲染的逻辑,即使在v2中实现了useState
也只实现了useState(initialValue)
带来的首屏渲染,在v3中我们终于实现状态更新啦,撒花🎉,新增功能如下:
-
useState hook
对单一HostComponent
的状态更新
ps:之所以只支持单一HostComponent
,是因为还没有实现key
以及diff
算法,所以无法支持多个兄弟组件的更新
🐛当一个组件中使用多个useState hook
且他们的更新函数同时触发,如示例中:
// 会造成页面逐渐卡顿并最终崩溃的例子
function App({name}) {
const [even, updateEven] = useState(0);
const [odd, updateOdd] = useState(1);
setTimeout(() => {
updateEven(even + 2);
updateOdd(odd + 2);
}, 2000);
return (
<ul>
<li key={0}>{even}</li>
<li key={1}>{odd}</li>
</ul>
)
}
react-on-the-way会造成页面逐渐卡顿并最终崩溃。原因是updateEven
和updateOdd
方法会分别开始一次新的更新流程。
在其中每次更新流程执行到updateFunctionComponent
时会调用App
函数,在函数内部会调用计时器并在2000ms后又调用这2个更新函数,从而又开启新的更新流程。更新流程的数量会指数增加并最终崩溃。
造成这个问题的原因是我们还没有实现React的任务优先级机制与任务的批处理。在React中,
- 同步模式下同一个事件函数内的同步更新会被批处理,只产生一次更新流程
- 异步模式下所有更新都会经过优先级调度
为了实现React的页面更新逻辑,需要改变状态(state),我们有2条路可选:
- 实现
ClassComopnent setState
- 实现
FunctionComopnent useState
考虑hook
是React的趋势,我们优先实现useState
,所以v2我们在第一版基础上增加了FunctionComponent
相关首屏渲染,新增功能如下:
-
FunctionComponent
类型组件的首屏渲染 -
hook
架构体系 -
useState hook
首屏渲染做的工作
我们的首要目标是实现React的页面更新逻辑,基于这个目标,我们首先实现了HostComponent
的首屏渲染,新增功能如下:
- Render-Commit整体架构体系
-
HostComponent
的首屏渲染
🙋♂️小讲堂:HostComponent
是指原生DOM组件对应的JSX,在React执行时产生的组件
// 比如这样
<div>Hello</div>