likes
comments
collection
share

React18内核探秘:手写React高质量源码迈向高阶开发

作者站长头像
站长
· 阅读数 17

手写 React 高质量源码,迈向高阶开发

核心代码,注释必读

// download:3w ukoou com

React 18中的新内容

React 18中的突破性变化

一个重要的版本如果没有突破性的变化会是什么?嗯,这个版本的React有点不同,你马上就会明白为什么。你可以做的改变之一是将render 改为createRoot ,像这样:

js
复制代码
// Before
import { render } from "react-dom"

const container = document.getElementById("app")
render(<App tab="home" />, container)

// After
import { createRoot } from "react-dom/client"

const container = document.getElementById("app")
const root = createRoot(container)
root.render(<App tab="home" />)

createRoot 启用React 18的并发功能。如果你不使用它,你的应用程序将表现得像在React 17上,你将无法体验到甜蜜的开箱即用的优化。所以现在,如果你还在使用 ,而不是 ,你会看到一个弃用通知。render createRoot

这是一个实验的好机会,看看新的并发功能是否能改善你的生产性能。你可以运行一个实验,其中一个变体有render ,另一个使用createRoot 。另外,你不会因为切换到新的API而破坏你的代码。你可以逐渐切换到createRoot ,而不会破坏你的应用程序。

为了确保你正确地迁移你的应用程序,尝试启用严格模式。严格模式会让你知道开发中的组件发生了什么,它会在控制台中打印出任何不正常的情况。启用严格模式不会影响生产构建。你可以在你的应用程序的某个地方这样做:

js
复制代码
import React from "react"
import { createRoot } from "react-dom/client"

function App() {
  return (
    <div>
      <Header />
      <React.StrictMode>
        <div>
          <Content />
          <SignUpForm />
        </div>
      </React.StrictMode>
      <Footer />
    </div>
  )
}

const container = document.getElementById("app")
const root = createRoot(container)
root.render(<App />)

React源码解读 scheduleWork

这里先scheduleWorkToRoot,这一步非常重要,他主要做了一下几个任务

找到当前Fiber的 root 给更新节点的父节点链上的每个节点的expirationTime设置为这个update的expirationTime,除非他本身时间要小于expirationTime 给更新节点的父节点链上的每个节点的childExpirationTime设置为这个update的expirationTime,除非他本身时间要小于expirationTime 最终返回 root 节点的Fiber对象

然后进入一个判断:

if ( !isWorking && nextRenderExpirationTime !== NoWork && expirationTime < nextRenderExpirationTime ) 我们来解释一下这几个变量的意思

isWorking代表是否正在工作,在开始renderRoot和commitRoot的时候会设置为 true,也就是在render和commit两个阶段都会为true nextRenderExpirationTime在是新的renderRoot的时候会被设置为当前任务的expirationTime,而且一旦他被,只有当下次任务是NoWork的时候他才会被再次设置为NoWork,当然最开始也是NoWork 那么这个条件就很明显了:目前没有任何任务在执行,并且之前有执行过任务,同时当前的任务比之前执行的任务过期时间要早(也就是优先级要高)

那么这种情况会出现在什么时候呢?答案就是:上一个任务是异步任务(优先级很低,超时时间是 502ms),并且在上一个时间片(初始是 33ms)任务没有执行完,而且等待下一次requestIdleCallback的时候新的任务进来了,并且超时时间很短(52ms 或者 22ms 甚至是 Sync),那么优先级就变成了先执行当前任务,也就意味着上一个任务被打断了(interrupted)

被打断的任务会从当前节点开始往上推出context,因为在 React 只有一个stack,而下一个任务会从头开始的,所以在开始之前需要清空之前任务的的stack。

context请看这里

unwindWork请看这里

然后重置所有的公共变量:

nextRoot = null
nextRenderExpirationTime = NoWork
nextLatestAbsoluteTimeoutMs = -1
nextRenderDidError = false
nextUnitOfWork = null
markPendingPriorityLevel
这个方法会记录当前的expirationTime到pendingTime,让expirationTime处于earliestPendingTime和latestPendingTime之间

并且会设置root.nextExpirationTimeToWorkOn和root.expirationTime = expirationTime分别是:

最早的pendingTime或者pingedTime,如果都没有则是lastestSuspendTime suspendedTime和nextExpirationTimeToWorkOn中较早的一个 调用 requestWork

if (
  !isWorking ||
  isCommitting ||
  nextRoot !== root
)

这个判断条件就比较简单了,!isWorking || isCommitting简单来说就是要么处于没有 work 的状态,要么只能在 render 阶段,不能处于 commit 阶段(比较好奇什么时候会在 commit 阶段有新的任务进来,commit 都是同步的无法打断)。还有一个选项nextRoot !== root,这个的意思就是你的 APP 如果有两个不同的 root,这时候也符合条件。