hook:useTransition
- React 18 新增的并发特性 Hook,用于标记低优先级的状态更新
- 核心作用: 让 UI 保持响应,避免因大量计算或渲染而卡顿
- 原理: 将状态更新标记为"transition",React 可以中断它来优先处理用户交互
基本语法
const [isPending, startTransition] = useTransition();
// isPending: 布尔值,表示 transition 是否正在进行中
// startTransition: 函数,用于包裹低优先级的状态更新
场景代码示例
-
【场景 1】大列表渲染优化(最常用):
-
输入框输入时,列表渲染不阻塞输入
❌ 不使用 useTransition - 输入卡顿
import { useState } from "react"; function SearchList() { const [input, setInput] = useState(""); const [list, setList] = useState([]); const handleChange = (e) => { const value = e.target.value; setInput(value); // 高优先级:立即更新输入框 // 低优先级:生成大列表(耗时操作) const newList = []; for (let i = 0; i < 20000; i++) { newList.push(value + " - " + i); } setList(newList); // ❌ 阻塞输入,造成卡顿 }; return ( <div> <input value={input} onChange={handleChange} /> {/* 输入时会卡顿,因为需要等待列表渲染完成 */} <ul> {list.map((item, i) => ( <li key={i}>{item}</li> ))} </ul> </div> ); }✅ 使用 useTransition - 输入流畅
import { useState, useTransition } from "react"; function SearchList() { const [input, setInput] = useState(""); const [list, setList] = useState([]); const [isPending, startTransition] = useTransition(); const handleChange = (e) => { const value = e.target.value; setInput(value); // 高优先级:立即更新输入框 // 低优先级:用 startTransition 包裹 startTransition(() => { const newList = []; for (let i = 0; i < 20000; i++) { newList.push(value + " - " + i); } setList(newList); // ✅ 不阻塞输入,保持响应 }); }; return ( <div> <input value={input} onChange={handleChange} /> {isPending && <div>加载中...</div>} {/* 显示加载状态 */} <ul style={{ opacity: isPending ? 0.5 : 1 }}> {" "} {/* 降低透明度 */} {list.map((item, i) => ( <li key={i}>{item}</li> ))} </ul> </div> ); } -
效果: 输入框立即响应,列表延迟更新,用户体验更好
-
-
【场景 2】选项卡切换(常用):
-
切换 Tab 时,保持界面响应
import { useState, useTransition } from "react"; function Tabs() { const [tab, setTab] = useState("home"); const [isPending, startTransition] = useTransition(); const handleTabClick = (newTab) => { startTransition(() => { setTab(newTab); // 标记为低优先级 }); }; return ( <div> <button onClick={() => handleTabClick("home")} disabled={isPending && tab !== "home"} > 首页 </button> <button onClick={() => handleTabClick("posts")} disabled={isPending && tab !== "posts"} > 文章 {isPending && tab === "posts" && "(加载中...)"} </button> <button onClick={() => handleTabClick("contact")} disabled={isPending && tab !== "contact"} > 联系 </button> <div style={{ opacity: isPending ? 0.5 : 1 }}> {tab === "home" && <HomePage />} {tab === "posts" && <PostsPage />} {/* 假设这是重量级组件 */} {tab === "contact" && <ContactPage />} </div> </div> ); } // 假设这是一个渲染缓慢的组件 function PostsPage() { const posts = Array.from({ length: 5000 }, (_, i) => `Post ${i}`); return ( <ul> {posts.map((post) => ( <li key={post}>{post}</li> ))} </ul> ); } -
效果: 点击按钮立即响应,内容延迟加载
-
-
【场景 3】搜索结果过滤(常用):
-
实时搜索时,保持输入流畅
import { useState, useTransition } from "react"; function SearchProducts() { const [query, setQuery] = useState(""); const [filteredProducts, setFilteredProducts] = useState(products); const [isPending, startTransition] = useTransition(); const handleSearch = (e) => { const value = e.target.value; setQuery(value); // 立即更新输入框 startTransition(() => { // 复杂的过滤逻辑 const filtered = products.filter((product) => product.name.toLowerCase().includes(value.toLowerCase()), ); setFilteredProducts(filtered); }); }; return ( <div> <input type="text" value={query} onChange={handleSearch} placeholder="搜索商品..." /> {isPending && <span>搜索中...</span>} <ul> {filteredProducts.map((product) => ( <li key={product.id}>{product.name}</li> ))} </ul> </div> ); } const products = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `商品 ${i}`, }));
-
-
【场景 4】配合 Suspense 使用(进阶):
-
与 Suspense 结合,实现更优雅的加载状态
import { useState, useTransition, Suspense } from "react"; function App() { const [tab, setTab] = useState("home"); const [isPending, startTransition] = useTransition(); const handleTabClick = (newTab) => { startTransition(() => { setTab(newTab); }); }; return ( <div> <button onClick={() => handleTabClick("home")}>首页</button> <button onClick={() => handleTabClick("profile")}> 个人资料 {isPending && "(加载中...)"} </button> <Suspense fallback={<div>Loading...</div>}> {tab === "home" && <Home />} {tab === "profile" && <Profile />} </Suspense> </div> ); }
-
useTransition vs setTimeout/debounce
| 方案 | 优先级控制 | 自动取消 | 状态提示 | React 优化 | 使用复杂度 |
|---|---|---|---|---|---|
| useTransition | ✅ 内置 | ✅ 自动 | ✅ isPending | ✅ 原生支持 | 低 |
| setTimeout | ❌ | ❌ 手动 | ❌ 需自己实现 | ❌ | 中 |
| debounce | ❌ | ✅ 部分支持 | ❌ 需自己实现 | ❌ | 中 |
选择原则:
- 需要保持 UI 响应 + 延迟更新 → 用 useTransition
- 减少 API 调用次数 → 用 debounce
- 简单延迟执行 → 用 setTimeout
什么时候使用 useTransition
| 场景 | 是否适用 | 原因 |
|---|---|---|
| 大列表渲染 | ✅ | 避免阻塞输入 |
| 复杂计算 | ✅ | 保持界面响应 |
| 选项卡切换 | ✅ | 切换体验更流畅 |
| 搜索/过滤 | ✅ | 输入不卡顿 |
| 路由切换 | ✅ | 页面切换更顺畅 |
| 简单的状态更新 | ❌ | 没必要,过度优化 |
| 网络请求 | ⚠️ | 配合 Suspense 使用更好 |
| 第三方库状态更新 | ❌ | useTransition 只作用于 React 状态 |
注意事项
-
⚠️ 只能包裹状态更新
// ❌ 错误:不能包裹异步操作 startTransition(async () => { const data = await fetchData(); setData(data); }); // ✅ 正确:只包裹状态更新 const data = await fetchData(); startTransition(() => { setData(data); }); -
⚠️ 不要滥用
// ❌ 不需要:简单的状态更新 startTransition(() => { setCount(count + 1); // 简单更新,没必要用 transition }); // ✅ 需要:复杂渲染 startTransition(() => { setList(generateHugeList()); // 复杂操作,适合用 transition }); -
⚠️ isPending 在 transition 期间为 true
const [isPending, startTransition] = useTransition(); startTransition(() => { setData(newData); }); // isPending 立即变为 true,直到 newData 渲染完成 console.log(isPending); // true (在 transition 期间) -
⚠️ transition 可以被中断
// React 可能会中断 transition 来处理更高优先级的更新 startTransition(() => { setHugeList(data); // 这个更新可能被中断 }); // 用户点击按钮(高优先级) onClick={() => setCount(count + 1)} // 会中断上面的 transition -
✅ 适合 CPU 密集型操作
// ✅ 适合:大量计算或渲染 startTransition(() => { setFilteredList(list.filter(/* 复杂过滤 */)); }); // ❌ 不适合:IO 密集型(网络请求) // 网络请求应该用其他方式优化 -
✅ 可以嵌套多个状态更新
startTransition(() => { setList(newList); setPage(newPage); setFilter(newFilter); // 所有这些更新都是低优先级的 }); -
✅ 配合 isPending 提供更好的用户反馈
return ( <div> <input onChange={handleChange} /> {isPending ? <div>搜索中...</div> : <div>{results.length} 条结果</div>} <div style={{ opacity: isPending ? 0.5 : 1 }}> {/* 降低透明度,而不是完全隐藏 */} {results.map((item) => ( <Item key={item.id} {...item} /> ))} </div> </div> );
实用技巧
-
技巧 1:自定义 Hook 封装
function useTransitionState(initialState) { const [state, setState] = useState(initialState); const [isPending, startTransition] = useTransition(); const setTransitionState = (value) => { startTransition(() => { setState(value); }); }; return [state, setTransitionState, isPending]; } // 使用 function App() { const [list, setList, isPending] = useTransitionState([]); const handleUpdate = () => { setList(generateHugeList()); // 自动包裹在 transition 中 }; return ( <div> <button onClick={handleUpdate}>更新列表</button> {isPending && <div>加载中...</div>} <List data={list} /> </div> ); } -
技巧 2:与 Loading 状态结合
function ProductList() { const [category, setCategory] = useState("all"); const [isPending, startTransition] = useTransition(); const [isLoading, setIsLoading] = useState(false); const handleCategoryChange = async (newCategory) => { setIsLoading(true); // 网络请求 const data = await fetchProducts(newCategory); // 状态更新用 transition startTransition(() => { setCategory(newCategory); setProducts(data); setIsLoading(false); }); }; // 显示 Loading:isLoading (网络) 或 isPending (渲染) const showLoading = isLoading || isPending; return ( <div> <select onChange={(e) => handleCategoryChange(e.target.value)}> <option value="all">全部</option> <option value="electronics">电子产品</option> </select> {showLoading && <div>加载中...</div>} <div style={{ opacity: isPending ? 0.5 : 1 }}>{/* 产品列表 */}</div> </div> ); }
浏览器兼容性
- React 18+ 支持
- 需要现代浏览器支持(ES2015+)
- 不支持 IE 11
记住:useTransition 让 React 可以中断低优先级的更新,优先处理用户交互,从而保持 UI 的响应性。