cache
const cachedFn = cache(fn);
cache
会以函数实参为键,缓存首次函数调用的结果,后续的函数调用也将直接使用缓存值。
cache
只能在服务端组件中使用,缓存的生命周期为单次服务器请求。fn
可以是异步函数。若fn
抛出错误,该错误也会被缓存。cache
会通过Object.is
来区分函数传参。cachedFn
可以跨组件使用并共享缓存。缓存访问是通过 context 实现的,所以在组件外调用cachedFn
不会读取或更新缓存。
如果 fn
是个比较缓慢的异步函数(比如数据请求),则可以先在组件的头部位置执行 cachedFn
:
const getData = cache(fetchData);
async function MyComponent() {
getData();
// 利用获取数据的时间做一些别的工作
await getData();
// ...
}
startTransition
startTransition(scope);
scope
应当是个同步的函数,内部可以调用多个 setState 状态更新。- 使用
startTransition
的状态更新会被标记为 transition 。transition 更新会被别的 state 更新打断。 - 不要将 transition 更新用于控制文本输入等行为中。
import { startTransition } from 'react';
function TabContainer() {
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
scope
中应当同步调用状态更新。一些错误使用:
startTransition(() => {
setTimeout(() => {
setPage('/about');
}, 1000);
});
startTransition(async () => {
await someAsyncFunction();
setPage('/about');
});
useTransition
const [isPending, startTransition] = useTransition();
isPending
- 是否有待处理的 transition 更新。
useDeferredValue
const deferredValue = useDeferredValue(value);
对于一些较慢的组件,可以用 useDeferredValue
优化,可以避免高频率的更新。
value
- 想延迟的值。value
更新时,React 仍会先用旧值渲染,然后在后台用新值渲染。这个后台渲染是可中断的,如果渲染期间value
再次被更新,React 则会丢弃先前的渲染并重新渲染。useDeferredValue
本身不会设立一个延迟的值,一旦value
更新就会立即在后台渲染。在提交到屏幕之前useDeferredValue
不会触发 effect 。- 性能开销较大的组件,一般也需要对其用
memo
包裹,才能得到真正的优化。
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
当 query
更新时:
- 首先,React 会使用新的
query
和旧的deferredQuery
重新渲染。 - 在后台,React 尝试用新的
deferredQuery
渲染。如果中途遇到了 suspense ,或者query
有了新的更新,则会中断放弃此次渲染(但期间产生的网络请求并不会因此中断)。
Suspense
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
下文中的「挂起」指的是英文 suspense 。
- 若 Suspense 被再次挂起,将会展示
fallback
,除非是startTransition
或useDeferredValue
引起的更新。 - 为了确保测量 DOM 布局的 effect 正常工作,Suspense 在挂起时会清理
children
里的 layout effect ,再次展示时也会重新触发 layout effect 。 - Suspense 允许嵌套,当组件挂起时会查找其最近的 Suspense 挂起。
在 children
加载数据时,Suspense 会被挂起。这里的 数据加载 仅限以下方式:
- 在支持 Suspense 的框架(如 Relay 或 Next.js)下请求数据(需参阅相应文档);
- 使用
lazy
加载组件代码; - 使用
use
读取 Promise 值。
function App() {
const [query, setQuery] = useState('');
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={query} />
</Suspense>
</>
);
}
如果想在更新时展示旧数据(而不是 fallback
),可以用 useDeferredValue
或 transition :
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
在服务端中,可以在组件中主动抛出错误,并用一个 Suspense 包裹:
<Suspense fallback={<Loading />}>
<Chat />
</Suspense>
function Chat() {
if (typeof window === 'undefined') {
throw Error('Chat 只能在客户端中渲染。');
}
// ……
}