上篇文章介绍了 Suspense
, 那么这篇文章就讲讲它的好搭档 useTransition
。若是你是 React 的粉丝,这两篇文章必定不能错过。html
咱们知道 React 内部作了翻天覆地的优化,外部也提供了一些紧凑的新 API,这些 API 主要用来优化用户体验。React 官方用一篇很长的文档《Concurrent UI Patterns 》 专门来介绍这一方面的动机和创造,其中的主角就是 useTransition
。react
相关文章git
本文大纲github
React 用’平行宇宙‘来比喻这个 useTransition 这个 API。What?shell
用 Git 分支来比喻会更好理解一点, 以下图,React 能够从当前视图(能够视做 Master
) 分支中 Fork
出来一个新的分支(尚且称为 Pending
),在这个新分支上进行更新,同时 Master
保持响应和更新,这两个分支就像'平行宇宙',二者互不干扰。当 Pending
分支准备'稳当',再合并(提交)到 Master
分支。redux
useTransition
就像一个时光隧道, 让组件进入一个平行宇宙,在这个平行宇宙中等待异步状态
(异步请求、延时、whatever)就绪。固然组件也不能无限期待在平行宇宙,useTranstion
能够配置超时时间,若是超时了,就算异步状态
未就绪也会被强制拉回现实世界。回到现实世界后,React 会当即对组件 Pengding 的变动进行合并,呈如今用户面前。浏览器
所以,你能够认为在Concurrent 模式下, React 组件有三种状态:缓存
你可能还不太能理解, 不要紧,继续往下读。bash
'平行宇宙'有什么用? 咱们不讲代码或者架构层次的东西。单从 UI
上看: 在某些 UI 交互场景,咱们并不想立刻将变动当即应用到页面上。架构
🔴好比你从一个页面切换到另外一个页面,新页面可能须要一些时间才能加载完成,其实咱们更乐于稍微停留在上一个页面,保持一些操做响应, 好比咱们能够取消,或者进行其余操做,而给我看一个什么都没有的空白页面或者空转加载状态符, 感受在作无谓的等待。
这种交互场景其实很是常见,眼前的例子就是浏览器:
还有咱们经常使用的 Github:
好比我想点击买个 AirPods
,浏览器会停留在上一个页面,直到下一个页面的请求得到响应或者超时。另外浏览器会经过地址栏的加载指示符提示请求状况。这种交互设计,比直接切换过去,展现一个空白的页面要好得多. 页面能够保持用户响应, 也能够随时取消请求,保留在原来的页面。
固然, Tab 切换时另一种交互场景,咱们但愿它立刻切换过去, 不然用户会以为点击不起做用。
'平行宇宙',还有一个好处: 🔴咱们假设大部分状况下,数据请求都是很是快的,这时候其实没有必要展现加载状态,这会致使页面闪烁和抖动。其实经过短暂的延时,能够来减小加载状态的展现频率。
另外,🔴useTransition 也能够用于包裹低优先级更新。 从目前的状况看,React 并无意愿暴露过多的 Concurrent 模式的底层细节。若是你要调度低优先级的更新,只能使用 useTransition。
如上图,咱们先按照 React 官方文档的描述来定义页面的各类状态。它提到页面加载有如下三个阶段:
① 过渡阶段(Transition)
指的是页面未就绪,等待加载关键数据的阶段。按照不一样的展现策略,页面能够有如下两种状态:
⚛️退化(Receded)。立刻将页面切换过去,展现一个大大的加载指示器或者空白页面。'退化'是什么意思? 按照 React 的说法是,页面本来有内容,如今变为无内容状态,这是一种退化,或者说历史的'退步'。
⚛️待定(Pending)。这是 useTransition
要达到的状态,即停留在当前页面,让当前页面保持响应。在关键数据准备就绪时进入 Skeleton
(骨架屏) 状态, 亦或者等待超时回退到 Receded
状态。
② 加载阶段(Loading)
指的是关键数据
已经准备就绪,能够开始展现页面的骨架或者框架部分。这个阶段有一个状态:
③就绪阶段(Done)。
指的是页面彻底加载完毕。这个阶段有一个状态:
传统的 React 中,当咱们变动状态进入一个新屏幕时,经历的是 🔴Receded
-> Skeleton
-> Complete
路径。在此以前要实现 🔴Pending
-> Skeleton
-> Complete
这种加载路径比较困难。 useTransition
能够改变这个局面。
接下来简单模拟一个页面切换,先来看默认状况下是如何加载的:
function A() {
return <div className="letter">A</div>;
}
function B() {
// ⚛️ 延迟加载2s,模拟异步数据请求
delay("B", 2000);
return <div className="letter">B</div>;
}
function C() {
// ⚛️ 延迟加载4s,模拟异步数据请求
delay("C", 4000);
return <div className="letter">C</div>;
}
// 页面1
function Page1() {
return <A />; } // 页面2 function Page2() { return ( <> <B /> <Suspense fallback={<div>Loading... C</div>}> <C /> </Suspense> </> ); } function App() { const [showPage2, setShowPage2] = useState(false); // 点击切换到页面2 const handleClick = () => setShowPage2(true) return ( <div className="App"> <div> <button onClick={handleClick}>切换</button> </div> <div className="page"> <Suspense fallback={<div>Loading ...</div>}> {!showPage2 ? <Page1 /> : <Page2 />} </Suspense> </div> </div> ); } 复制代码
看一下运行效果:
点击切换后,咱们会立刻看到一个大大的 Loading...
,接着 2s 后 B 加载完毕,再等待 2s 后 C 加载完毕。这个过程就是 Receded
-> Skeleton
-> Complete
如今有请 useTransition 隆重登场 🎉,只需对上面的代码进行的简单改造:
// ⚛️ 导入 useTransition
import React, { Suspense, useState, useTransition } from "react";
function App() {
const [showPage2, setShowPage2] = useState(false);
// ⚛️ useTransition 接收一个超时时间,返回一个startTransition 函数,以及一个 pending
const [startTransition, pending] = useTransition({ timeoutMs: 10000 });
const handleClick = () =>
// ⚛️ 将可能触发 Suspense 挂起的状态变动包裹在 startTransition 中
startTransition(() => {
setShowPage2(true);
});
return (
<div className="App"> <div> <button onClick={handleClick}>切换</button> {/* ⚛️ pending 表示处于待定状态, 你能够进行一些轻微的提示 */} {pending && <span>切换中...</span>} </div> <div className="page"> <Suspense fallback={<div>Loading ...</div>}> {!showPage2 ? <Page1 /> : <Page2 />} </Suspense> </div> </div>
);
}
复制代码
useTransition Hook 的API比较简洁,有4个须要关键的点:
timeoutMs
, 表示切换的超时时间(最长在平行宇宙存在的时间),useTransition 会让 React 保持在当前页面,直到被触发 Suspense 就绪或者超时。
startTransition
, 将可能触发页面切换(严格说是触发 Suspense 挂起)的状态变动包裹在 startTransition
下,实际上 startTransition 提供了一个'更新的上下文'。 下一节咱们会深刻探索这里面的细节
pending
, 表示正处于待定状态。咱们能够经过这个状态值,适当地给用户一下提示。
Suspense
, useTransition 实现过渡状态必须和 Suspense 配合,也就是 startTransition
中的更新必须触发任意一个 Suspense 挂起。
看一下实际的运行效果吧!
能够在这个 CodeSandbox 查看运行效果
这个效果彻底跟本节开始的'第一张图'同样: React 会保留在当前页面,pending
变为了true,接着 B 先就绪,界面立刻切换过去。整个过程符合 Pending
-> Skeleton
-> Complete
的路径。
startTransition
中的变动
一旦触发 Suspense
,React 就会将变动
标记的 Pending 状态, React会延后 ’提交‘ 这些变动。因此实际上并无开头说的平行宇宙, 那么高大上和神奇,React 只不过是延后了这些变动的提交。咱们界面上看到的只不过是旧的或者未被 Pending 的状态,React 在后台进行了预渲染。
注意,React 只是暂时没有提交这些变动,不说明 React ’卡死了‘,处于Pending 状态的组件还会接收用户的响应,进行新的状态变动,新的状态更新也能够覆盖或终止 Pending 状态。
总结一下进入和退出 Pending 状态的条件:
状态变动
包裹在 startTransition
下,且这些更新会触发 Suspense 挂起这一节,咱们深刻探索一下 useTransition,可是方式不是去折腾源码,而是把它当成一个黑盒,经过几个实验来加深你对 useTransition 的理解。
useTransition 的前身是 withSuspenseConfig
, Sebmarkbage 在今年五月份提的一个PR 中引进了它。
从命名上看,它不过是想配置一下 Suspense。 咱们也能够经过最新的源码验证这一点。 useTransition 的工做'看似'很是简单:
function updateTransition(
config: SuspenseConfig | void | null,
): [(() => void) => void, boolean] {
const [isPending, setPending] = updateState(false); // 至关于useState
const startTransition = updateCallback( // 至关于useCallback
callback => {
setPending(true); // 设置 pending 为 true
// 以低优先级调度执行
Scheduler.unstable_next(() => {
// ⚛️ 设置suspenseConfig
const previousConfig = ReactCurrentBatchConfig.suspense;
ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
try {
// 还原 pending
setPending(false);
// 执行你的回调
callback();
} finally {
// ⚛️ 还原suspenseConfig
ReactCurrentBatchConfig.suspense = previousConfig;
}
});
},
[config, isPending],
);
return [startTransition, isPending];
}
复制代码
看似很普通,要点在哪?Sebmarkbage 在上述的 PR 中也说起了一些信息。
startTransition 一开始执行就将 pending 设置为true。接着使用 unstable_next
执行回调, unstable_next 能够下降更新的优先级。也就是说 unstable_next 回调中触发的’变动‘优先级会比较低,它会让位为高优先级的更新,或者当前事务繁忙时,调度到下一空闲期再应用,但也可能立刻就被应用。
要点是 ReactCurrentBatchConfig.suspense
的配置, 这里面会配置 Suspense 的超时时间。它代表这个区间触发的变动都被关联该 suspenseConfig
, 这些变动会根据 suspenseConfig 来计算本身的 expiredTime
(能够视做‘优先级’)。咱们暂且将这些关联了 suspenseConfig 的变动称为 Pending 变动
.
Pending 变动
触发的从新渲染(Render)也会关联该 suspenseConfig
。若是在渲染期间触发了 Suspense,那么Pending 变动
就会被延迟提交(commit),它们会缓存在内存中, 等到 Suspense 超时或者就绪, 抑或被其余更新覆盖, 才强制提交到用户界面。
Pending 变动
只是被延迟提交了,可是不会影响最终数据和视图的一致性。React 会在内存中从新渲染,只是不提交到用户界面而已。
React 内部的实现太过复杂,我发现去挖它或者用文字表达出来成本都很高。所以换一种方式,经过实验(黑盒)方式来了解它的行为:
这些实验代码在这个 CodeSandbox 中
这个实验主要用于验证 unstable_next
, 它会让下降更新的优先级。经过下面的实验咱们会观察到: 经过startTransition
包裹的变动在任务繁忙的状况会稍微延后更新,可是最终状态是一致的。
实验代码:
export default function App() {
const [count, setCount] = useState(0);
const [tick, setTick] = useState(0);
const [startTransition, pending] = useTransition({ timeoutMs: 10000 });
const handleClick = () => {
// ⚛️ 同步更新
setCount(count + 1);
startTransition(() => {
// ⚛️ 低优先级更新 tick
setTick(t => t + 1);
});
};
return (
<div className="App"> <h1>Hello useTransition</h1> <div> <button onClick={handleClick}>ADD + 1</button> {pending && <span>pending</span>} </div> <div>Count: {count}</div> {/* ⚛️ 这是一个复杂的组件,渲染须要一点时间,模拟繁忙的状况 */} <ComplexComponent value={tick} /> </div> ); } 复制代码
实验结果以下:
在连续点击的状况下,ComplexComponent
的更新会明显滞后,这是由于 tick 变动会被延后和合并,可是最后它们的结果是一致的.
export default function App() {
const [count, setCount] = useState(0);
const [tick, setTick] = useState(0);
const [startTransition, pending] = useTransition({ timeoutMs: 10000 });
const handleClick = () => {
startTransition(() => {
setCount(c => c + 1);
setTick(c => c + 1);
});
};
return (
<div className="App">
<h1>Hello useTransition {tick}</h1>
<div>
<button onClick={handleClick}>ADD + 1</button>
{pending && <span className="pending">pending</span>}
</div>
<Tick />
<SuspenseBoundary id={count} />
</div>
);
}
const SuspenseBoundary = ({ id }) => {
return (
<Suspense fallback="Loading...">
{/* 这里会抛出一个Promise异常,3s 后 resolved */}
<ComponentThatThrowPromise id={id} />
</Suspense>
);
};
// Tick 组件每秒递增一次
const Tick = ({ duration = 1000 }) => {
const [tick, setTick] = useState(0);
useEffect(() => {
const t = setInterval(() => {
setTick(tick => tick + 1);
}, duration);
return () => clearInterval(t);
}, [duration]);
return <div className="tick">tick: {tick}</div>;
};
复制代码
当咱们点击按钮时会递增 count 和 tick, count 会传递给 SuspenseBoundary,从而触发 Suspense。
经过上面的结果能够知道,在 startTransition 中进行了变动(携带suspenseConfig), 对应的从新渲染触发了 Suspense,因此进入了Pending状态,它们渲染结果不会被当即‘提交’,页面仍是保持在原来的状态。
另外你会发现 App 组件的 tick 跟 SuspenseBoundary 同样也会被‘中止’(看Hello Transition 后面的tick),由于 tick 变动也关联了suspenseConfig。
而 Tick 组件则每一秒递增一次,不会被阻塞。
这就说明了一旦触发了Suspense,只要关联了 suspenseConfig 的变动就会被‘暂停’提交。
在 2️⃣ 的基础上,将 setTick 提到 startTransition 做用域外:
export default function App() {
const [count, setCount] = useState(0);
const [tick, setTick] = useState(0);
const [startTransition, pending] = useTransition({ timeoutMs: 10000 });
console.log("App rendering with", count, tick, pending);
const handleClick = () => {
setTick(c => c + 1);
startTransition(() => {
setCount(c => c + 1);
});
};
const handleAddTick = () => setTick(c => c + 1);
useEffect(() => {
console.log("App committed with", count, tick, pending);
});
return (
<div className="App"> <h1>Hello useTransition {tick}</h1> <div> <button onClick={handleClick}>ADD + 1</button> <button onClick={handleAddTick}>Tick + 1</button> {pending && <span className="pending">pending</span>} </div> <Tick /> <SuspenseBoundary id={count} /> </div> ); } 复制代码
如今 tick 会被当即更新,而 SuspenseBoundary 还会挂在 pending 状态。
咱们打开控制台看一下,输出状况:
App rendering with 1 2 true # pending 被设置为true, count 这是时候是 1, 而 tick 是 2
App rendering with 1 2 true
read 1
App committed with 1 2 true # 进入Pending 状态以前的一次提交,咱们在这里开始展现 pending 指示符
# 下面 Tick 更新了三次(3s)
# 咱们注意到,每一次 React 都会从新渲染一下 App 组件,即 'ping' 一下处于 Pending 状态的组件, 检查一下是否‘就绪’(没有触发Suspense)
# 若是还触发 Suspense, 说明还要继续等待,这些从新渲染的结果不会被提交
App rendering with 2 2 false # ping, 这里count变成了2,且 pending 变成了 false
App rendering with 2 2 false # 可是 React 在内存中渲染它们,咱们看不到
read 2
Tick rendering with 76 # Tick 从新渲染
Tick rendering with 76
Tick committed with 76 # 提交 Tick 更新,刷新到界面上
App rendering with 2 2 false # ping 仍是没有就绪,继续 pending
App rendering with 2 2 false
read 2
Tick rendering with 77
Tick rendering with 77
Tick committed with 77
App rendering with 2 2 false # ping
App rendering with 2 2 false
read 2
Tick rendering with 78
Tick rendering with 78
Tick committed with 78
App rendering with 2 2 false # ping
App rendering with 2 2 false
read 2
# Ok, Promise 已经就绪了,这时候再一次从新渲染 App
# 此次没有触发 Suspense,React 会立刻提交用户界面
App rendering with 2 2 false
App rendering with 2 2 false
read 2
App committed with 2 2 false
复制代码
经过上面的日志,咱们能够清晰地理解 Pending 组件的更新行为
在3️⃣的基础上,将 SuspenseBoundary 改写为 DoubleSuspenseBoundary, 这里会嵌套一个 Suspense 加载一个更耗时的资源:
const DoubleSuspenseBoundary = ({ id }) => {
return (
<Suspense fallback={<div>Loading...</div>}>
{/* 须要加载 2s */}
<ComponentThatThrowPromise id={id} timeout={2000} />
<Suspense fallback={<div>Loading second...</div>}>
{/* 须要加载 4s */}
<ComponentThatThrowPromise id={id + "second"} timeout={4000} />
</Suspense>
</Suspense>
)
}
复制代码
测试一下效果:
首先注意观察首次挂载,Suspense 首次挂载时不会触发延迟提交,所以咱们首先会看到 Loading...
、接着第一个 ComponentThatThrowPromise
加载完毕,显示ComponentThatThrowPromise id: 0
和 Loading second...
, 最后彻底加载完毕。
接着咱们点击按钮,这时候 DoubleSuspenseBoundary 会保持不动,等待 5s 后(也就是第二个ComponentThatThrowPromise
加载完毕), 才提交。
理想的效果是跟首次挂载的时候同样:在第一个 ComponentThatThrowPromise 就绪时就切换过来,不用等待第二个加载完毕。
感受有点不对?我这这里想了好久, 官方文档上 Concurrent UI Patterns (Experimental) - Wrap Lazy Features in <Suspense> 说了,第二个ComponentThatThrowPromise
已经嵌套在 Suspense
中了,理论上应该不会阻塞提交。
回到开头的第一句话:'Suspense 首次挂载时不会触发延迟提交'。咱们再试一下, 给 DoubleSuspenseBoundary 设置一个key,强制让它销毁从新建立:
export default function App() {
// .....
return (
<div className="App"> <h1>Hello useTransition {tick}</h1> <div> <button onClick={handleClick}>ADD + 1</button> {pending && <span className="pending">pending</span>} </div> <Tick /> {/* ⚛️ 这里添加key,强制从新销毁建立 */} <DoubleSuspenseBoundary id={count} key={count} /> </div> ) } 复制代码
试一下效果:
咱们发现,每次点击都是Loading...
, Pending 状态没有了! 由于每次 count
递增, DoubleSuspenseBoundary
就会从新建立,不会触发延迟提交。
基于这个原理,咱们能够再改造一下 DoubleSuspenseBoundary
, 这一次,咱们只给嵌套的 Suspense
加上key,让它们从新建立不阻塞 Pending 状态.
const DoubleSuspenseBoundary = ({ id }) => {
return (
<Suspense fallback={<div>Loading...</div>}>
<ComponentThatThrowPromise id={id} timeout={2000} />
{/* ⚛️ 咱们不但愿这个 Suspense 阻塞 pending 状态, 给它加个key, 让它强制从新建立 */}
<Suspense key={id} fallback={<div>Loading second...</div>}>
<ComponentThatThrowPromise id={id + "second"} timeout={4000} />
</Suspense>
</Suspense>
);
};
复制代码
最后的效果
It's work! 🍻
我也不知道,测试一下:
mport React, { useTransition, useEffect } from "react";
import { createStore } from "redux";
import { Provider, useSelector, useDispatch } from "react-redux";
import SuspenseBoundary from "./SuspenseBoundary";
import Tick from "./Tick";
const initialState = { count: 0, tick: 0 };
const ADD_TICK = "ADD_TICK";
const ADD_COUNT = "ADD_COUNT";
const store = createStore((state = initialState, action) => {
const copy = { ...state };
if (action.type === ADD_TICK) {
copy.tick++;
} else {
copy.count++;
}
return copy
});
export const Page = () => {
const { count, tick } = useSelector(({ tick, count }) => ({ tick, count }));
const dispatch = useDispatch();
const [startTransition, pending] = useTransition({ timeoutMs: 10000 });
const addTick = () => dispatch({ type: ADD_TICK });
const addCount = () => dispatch({ type: ADD_COUNT });
const handleClick = () => {
addTick();
startTransition(() => {
console.log("Start transition with count: ", count);
addCount();
console.log("End transition");
});
};
console.log(`App rendering with count(${count}) pendig(${pending})`);
useEffect(() => {
console.log("committed with", count, tick, pending);
});
return (
<div className="App"> <h1>Hello useTransition {tick}</h1> <div> <button onClick={handleClick}>ADD + 1</button> {pending && <span className="pending">pending</span>} </div> <Tick /> <SuspenseBoundary id={count} /> </div> ); }; export default () => { return ( <Provider store={store}> <Page /> </Provider> ); }; 复制代码
先看一下运行效果:
What’s the problem? 整个界面都 Pending
了, 整个界面不仅仅指 App
这颗子树,并且 Tick 也不走了。打开控制台看到了一个警告:
Warning: Page triggered a user-blocking update that suspended.
The fix is to split the update into multiple parts: a user-blocking update to provide immediate feedback, and another update that triggers the bulk of the changes.
Refer to the documentation for useTransition to learn how to implement this pattern.
复制代码
先来看一下目前Rudux 和 Mobx 的Hooks API 是怎么更新的,本质上它们都采用订阅机制,在事件触发后进行强制更新, 基本结构以下:
function useSomeOutsideStore() {
// 获取外部 store
const store = getOutsideStore()
const [, forceUpdate] = useReducer(s => s + 1, 0)
// ⚛️ 订阅外部数据源
useEffect(() => {
const disposer = store.subscribe(() => {
// ⚛️ 强制更新
forceUpdate()
))
return disposer
}, [store])
// ...
}
复制代码
也就是说,咱们在 startTransition
中更新 Redux 状态时,会同步接收到事件,而后调用 forceUpdate
。forceUpdate
才是真正在 suspenseConfig 上下文中变动的状态。
咱们再看一下控制台日志:
Start transition with count 0
End transition
App rendering with count(1) pendig(true) # 这里出问题了 🔴, 你能够和实验 3️⃣ 中的日志对比一下
App rendering with count(1) pendig(true) # 实验 3️⃣ 中这里的 count 是 0,而这里的count是1,说明没有 defer!
read 1
Warning: App triggered a user-blocking update that suspended.
The fix is to split the update into multiple parts: a user-blocking update to provide immediate feedback, and another update that triggers the bulk of the changes.
Refer to the documentation for useTransition to learn how to implement this pattern.
复制代码
经过日志能够基本上可以定位出问题,count 没有被延迟更新,因此致使'同步'触发了 Suspense,这也是 React 警告的缘由。 因为 useTransition 目前还处于实验阶段,若是不是 startTransition 上下文中的状态更新致使的Suspense,行为仍是未肯定的。
可是最终的行为有点玄学,它会致使整个应用被‘Pending’,全部状态更新都不会被提交。这块我也很疑惑,没有精力深究下去,只能等待后续官方的更新,读者也能够去琢磨琢磨。
所以,暂时不推荐将会触发 Suspense 的状态放置在 Redux 或者 Mobx 中。
最后再重申一下, useTransition
要进入 Pending
状态要符合如下几个条件:
startTransition
做用域下, 这些更新会关联 suspenseConfig
Suspense
Suspense
不是首次挂载若是你理解了上面的内容, 那么 useDeferedValue
就好办了,它不过是 useTransition 的简单封装:
function useDeferredValue<T>( value: T, config: TimeoutConfig | void | null, ): T {
const [prevValue, setValue] = useState(value);
const [startTransition] = useTransition(config)
// ⚛️ useDeferredValue 只不过是监听 value 的变化,
// 而后在 startTransition 中更新它。从而实现延迟更新的效果
useEffect(
() => {
startTransition(() => {
setValue(value);
})
},
[value, config],
);
return prevValue;
}
复制代码
useDeferredValue
只不过是使用 useEffect 监听 value
的变化, 而后在 startTransition 中更新它。从而实现延迟更新的效果。上文实验 1️⃣ 已经介绍过运行效果,React 会下降 startTransition 中更新的优先级, 这意味着在事务繁忙时它们会延后执行。
咱们一开始介绍了 useTransition 的应用场景, 让页面实现 Pending
-> Skeleton
-> Complete
的更新路径, 用户在切换页面时能够停留在当前页面,让页面保持响应。 相比展现一个无用的空白页面或者加载状态,这种用户体验更加友好。
固然上述的假设条件时数据加载很慢,若是数据加载很快,利用 useTransition 机制,咱们实现不让用户看到加载状态,这样能避免页面页面抖动和闪烁, 看起来像没有加载的过程。
接着咱们简单介绍了 useTransition 的运行原理和条件。 若是 startTransition 中的状态更新触发了 Suspense,那么对应的组件就会进入 Pending 状态。在 Pending 状态期间,startTransition中设置变动都会被延迟提交。 Pending 状态会持续到 Suspense 就绪或者超时。
useTransition 必须和 Suspense 配合使用才能施展魔法。还有一个用户场景是咱们能够将低优先级的更新放置到 startTransition 中。好比某个更新的成本很高,就能够选择放到 startTransition 中, 这些更新会让位高优先级的任务,另外会 React 延迟或合并一个比较复杂的更新,让页面保持响应。
Ok,关于 Concurrent 模式的介绍就先告一段落了, 这是中文的第一手资料。写这些文章耗掉了我大部分的业余时间,若是你喜欢个人文章,请多给我点赞和反馈。