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