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

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

核心代码,注释必读

// download:3w ukoou com

React 18中的新内容

React 18中的突破性变化

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

javascript 复制代码
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 ,而不会破坏你的应用程序。

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

javascript 复制代码
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请看这里

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

ini 复制代码
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

yaml 复制代码
if (
  !isWorking ||
  isCommitting ||
  nextRoot !== root
)

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

相关推荐
学前端搞口饭吃2 小时前
react context如何使用
前端·javascript·react.js
GDAL2 小时前
为什么Cesium不使用vue或者react,而是 保留 Knockout
前端·vue.js·react.js
Dragon Wu12 小时前
React state在setInterval里未获取最新值的问题
前端·javascript·react.js·前端框架
YU大宗师12 小时前
React面试题
前端·javascript·react.js
木兮xg12 小时前
react基础篇
前端·react.js·前端框架
三思而后行,慎承诺14 小时前
Reactnative实现远程热更新的原理是什么
javascript·react native·react.js
知识分享小能手14 小时前
React学习教程,从入门到精通,React 组件生命周期详解(适用于 React 16.3+,推荐函数组件 + Hooks)(17)
前端·javascript·vue.js·学习·react.js·前端框架·vue3
夏天199517 小时前
React:聊一聊状态管理
前端·javascript·react.js
LFly_ice17 小时前
学习React-11-useDeferredValue
前端·学习·react.js