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,这时候也符合条件。

相关推荐
凯哥爱吃皮皮虾7 小时前
如何给 react 组件写单测
前端·react.js·jest
每一天,每一步10 小时前
react antd点击table单元格文字下载指定的excel路径
前端·react.js·excel
screct_demo20 小时前
詳細講一下在RN(ReactNative)中,6個比較常用的組件以及詳細的用法
javascript·react native·react.js
光头程序员1 天前
grid 布局react组件可以循数据自定义渲染某个数据 ,或插入某些数据在某个索引下
javascript·react.js·ecmascript
limit for me1 天前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者1 天前
如何构建一个简单的React应用?
前端·react.js·前端框架
VillanelleS1 天前
React进阶之高阶组件HOC、react hooks、自定义hooks
前端·react.js·前端框架
某哈压力大1 天前
基于react-vant实现弹窗搜索功能
前端·react.js
傻小胖1 天前
React 中hooks之useInsertionEffect用法总结
前端·javascript·react.js
flying robot2 天前
React的响应式
前端·javascript·react.js