/react-on-the-way

基于V16.13.1,从0实现React

Primary LanguageJavaScript

React-On-The-Way

React-On-The-Way

基于ReactV16.13.1架构,从零实现React 🎉🎉🎉

配套教程

React技术揭秘

👋👋👋 文章有任何不清楚的地方,欢迎给我提PR

为什么会有这个仓库?

我假设React是你日常开发的框架,在日复一日的开发中,你萌生了学习React源码的念头,在网上搜各种源码解析后,你发现这些教程可以分为2类:

  1. 《xx行代码带你实现迷你React》,《xx行代码实现React hook》这样短小精干的文章。如果你只是想花一点点时间了解下React的工作原理,我向你推荐 这篇文章,非常精彩。同时,这个仓库可能不适合你,因为他会花掉你很多时间。

  2. 《React Fiber原理》,《React expirationTime原理》这样摘录React源码讲解的文章。如果你想学习React源码,当你都不知道Fiber是什么,不知道expirationTime对于React的意义时,这样的文章会给人“你讲解的代码我看懂了,但这些代码的作用是什么”的感觉。

这个仓库的存在就是为了解决这个问题。

简单来说,这个仓库有对应的一系列文章,文章会讲解React为什么要这么做,以及大体怎么做,但不会有大段的代码告诉你怎么做。

当你看完文章知道我们要做什么后,再来看仓库中具体的代码实现。

同时为了防止堆砌过多功能后,代码量太大影响你理解某个功能的实现,我为每个功能的实现打上一个git tag

历史版本预览

通过切换git tag浏览不同完成度的项目,执行npm start启动该版本下的Demo

当前版本v6

v6 diff v5

v6实现了React的异步调度器Scheduler(也就是说我们实现了requestIdleCallback polyfill),并使用Scheduler实现了异步render,也就是React ConcurrentMode

之前的版本中,我们都是同步执行render流程。在v6中,我们会为产生的update赋予一个优先级,高优先级的update会优先进入render流程。甚至当低优先级的update在render过程中我们触发了高优先级update,这时会搁置低优先级render转而处理高优先级render,这很酷,不是么😄

相对应的,v6相对v5增加了大量代码和一些全局变量。不过没关系,我会在之后的文章介绍这一切是如何做到的。新增功能如下:

  1. Scheduler模块
  2. fiber的优先级冒泡机制
  3. ConcurrentMode

这真是React内部最复杂的机制了,让人头秃👨‍🦲

v5

v5 diff v4

在v3中我们实现了状态更新,直接在FunctionComponent函数体内触发更新会造成死循环,所以我们用计时器来触发。在业务中,我们一般是通过:

  1. 回调函数(ex:onClick)
  2. useEffect hook
  3. ClassComponent生命周期钩子

来触发。既然我们已经实现了useState hook,这一版我们就实现useEffect hook,新增功能如下:

  • useEffect hook首屏及再次渲染的完整逻辑

v4

v4 diff v3

之前只能更新单一节点,这次实现了大名鼎鼎的React Diff算法,可以更新多个兄弟子节点了😄,新增功能如下:

  • 节点支持keyprop
  • commit流程支持Deletion effectTag处理
  • reconcileChildrenArray支持非首次渲染的diff算法

ps:支持Deletion effectTag处理是为了应对:

// 首屏渲染的组件
[a, b, c] 
// 再次渲染的组件
[a, null, c] 

在这种情况下b fiber被标记为Deletion effectTag,对应的DOM节点需要删除

v3

v3 diff v2

之前的版本只实现了首屏渲染的逻辑,即使在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会造成页面逐渐卡顿并最终崩溃。原因是updateEvenupdateOdd方法会分别开始一次新的更新流程。

在其中每次更新流程执行到updateFunctionComponent时会调用App函数,在函数内部会调用计时器并在2000ms后又调用这2个更新函数,从而又开启新的更新流程。更新流程的数量会指数增加并最终崩溃。

造成这个问题的原因是我们还没有实现React的任务优先级机制与任务的批处理。在React中,

  • 同步模式下同一个事件函数内的同步更新会被批处理,只产生一次更新流程
  • 异步模式下所有更新都会经过优先级调度

v2

v2 diff v1

为了实现React的页面更新逻辑,需要改变状态(state),我们有2条路可选:

  1. 实现ClassComopnent setState
  2. 实现FunctionComopnent useState

考虑hook是React的趋势,我们优先实现useState,所以v2我们在第一版基础上增加了FunctionComponent相关首屏渲染,新增功能如下:

  • FunctionComponent类型组件的首屏渲染
  • hook架构体系
  • useState hook首屏渲染做的工作

v1

我们的首要目标是实现React的页面更新逻辑,基于这个目标,我们首先实现了HostComponent的首屏渲染,新增功能如下:

  • Render-Commit整体架构体系
  • HostComponent的首屏渲染

🙋‍♂️小讲堂:HostComponent是指原生DOM组件对应的JSX,在React执行时产生的组件

// 比如这样
<div>Hello</div>