useTransition Hook 和 Actions
使用异步过渡(useTransition)的函数被称为 Actions(函数命名应以 Action 结尾)
js
// 使用 Actions 中的待定状态
const [isPending, startTransition] = useTransition()
const handleSubmit = () => {
startTransition(async () => {
// 请求接口
})
}
return (
<button onClick={handleSubmit} disabled={isPending}>提交</button>
)
Actions 做了什么?自动管理数据提交
- 待定状态:提供待定状态功能类似于 loading
- 乐观更新:提供 useOptimistic Hook
- 错误处理
- 表单:和 form 元素结合,可以将 Actions 传递给 form 的 action 和 formAction 属性(官方例子中,普通函数传递给表单会被自动包裹在 startTransition 中)
引申出 useOptimistic Hook
目的是为了消除网络等待的延迟感,而不是限制函数的触发频率,不可理解为防抖或节流。
- 防抖:多次触发,"最后一次"操作达到时间间隔后执行
- 节流:多次触发,一段时间间隔执行一次
- useOptimistic:先前端更新最新值,让用户感觉瞬间完成了操作
useTransition 和 useOptimistic 都可以获取 pending 状态,区别在于前者会提供 startTransition 函数,后者可以在任何异步过渡中使用。
useOptimistic Hook 可以接收多个 values 和 reducer
js
const [optimisticState, updateOptimistic] = useOptimistic(
{ isFollowing: user.isFollowing, followerCount: user.followerCount },
(current, isFollowing) => ({
isFollowing,
followerCount: current.followerCount + (isFollowing ? 1 : -1)
})
);
使用 useOptimistic 最好使用 reducer 相对更新而不是绝对赋值
js
// May show stale data if state changes during Action
const [optimistic, setOptimistic] = useOptimistic(count);
setOptimistic(5); // Always sets to 5, even if count changed
// Better: relative updates handle state changes correctly
const [optimistic, adjust] = useOptimistic(count, (current, delta) => current + delta);
adjust(1); // Always adds 1 to whatever the current count is
useDeferredValue Hook
在新内容加载期间显示旧内容,同时表明旧内容已过时
js
import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<div style={{
opacity: isStale ? 0.5 : 1,
transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear'
}}>
<SearchResults query={deferredQuery} />
</div>
</Suspense>
</>
);
}
延迟渲染 UI 的某些部分,比如一个文本框和一个列表,不希望列表的渲染阻碍输入框的输入,这并没有让 SlowList 的重新渲染变快,只是告诉 React 可以将列表的重新渲染优先级降低,不阻碍按键输入。
js
import { useState, useDeferredValue } from 'react';
import SlowList from './SlowList.js';
export default function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}
// 组件需包裹在 memo 里
import { memo } from 'react';
const SlowList = memo(function SlowList({ text }) {
// 仅打印一次。实际的减速是在 SlowItem 组件内部。
console.log('[ARTIFICIALLY SLOW] Rendering 250 <SlowItem />');
let items = [];
for (let i = 0; i < 250; i++) {
items.push(<SlowItem key={i} text={text} />);
}
return (
<ul className="items">
{items}
</ul>
);
});
function SlowItem({ text }) {
let startTime = performance.now();
while (performance.now() - startTime < 1) {
// 每个 item 暂停 1ms,模拟极其缓慢的代码
}
return (
<li className="item">
Text: {text}
</li>
)
}
export default SlowList;
引申出 Suspense 组件
当内容正在加载时显示后备方案,只有使用了 Suspense 数据源(支持 Suspense 的框架 Relay 和 Next.js;使用 Lazy 懒加载组件;使用 use 读取 Promise 值)才会激活 Suspense 组件。
允许嵌套多个 Suspense 组件创建一个加载序列逐步加载内容
js
import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';
export default function ArtistPage({ artist }) {
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<BigSpinner />}>
<Biography artistId={artist.id} />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums artistId={artist.id} />
</Panel>
</Suspense>
</Suspense>
</>
);
}
function BigSpinner() {
return <h2>🌀 Loading...</h2>;
}
function AlbumsGlimmer() {
return (
<div className="glimmer-panel">
<div className="glimmer-line" />
<div className="glimmer-line" />
<div className="glimmer-line" />
</div>
);
}
加载序列将会是:
- 如果
Biography没有加载完成,BigSpinner会显示在整个内容区域的位置。 - 一旦
Biography加载完成,BigSpinner会被内容替换。 - 如果
Albums没有加载完成,AlbumsGlimmer会显示在Albums和它的父级Panel的位置。 - 最后,一旦
Albums加载完成,它会替换AlbumsGlimmer。
和 useDeferredValue 组合使用也可实现之前使用了 memo 组件例子的同样效果