use() 是 React 19 引入的一个全新 Hook,它打破了传统 Hooks 的两个重要限制:不再局限于组件顶层调用,并且能够直接在渲染期间读取异步数据(Promise)和同步上下文(Context),统一了两种最常见的“读取外部资源”的模式。
为什么需要 use()
在 React 19 之前,读取异步数据必须借助 useEffect 或第三方库(如 TanStack Query),而读取 Context 必须使用 useContext。这两者虽然都能工作,但存在一些痛点:
- 异步数据与 Suspense 割裂:想让组件“等待”一个 Promise 完成后再渲染,过去只能通过实验性的
Suspense集成或第三方方案,没有官方、轻量的 API。 - 条件/Hooks 中的灵活读取:
useContext必须写在组件顶层,不能在条件分支、循环或普通函数中调用。有时你只想在某个事件处理器中临时读取一下 Context,却不得不变通。 - Server Components 与 Client Components 的边界:在 RSC(React Server Components)中,直接在服务端
await数据然后传给客户端组件是最自然的模式,但客户端组件自身也需要一种轻量的、与 Suspense 深度集成的异步数据读取方式。
use() 应运而生:它专门用于在组件渲染期间读取 React 的“资源”——既可以是 Promise,也可以是 Context。
读取 Promise:原生支持的“渲染即等待”
use() 最革命性的地方是:你可以直接向它传入一个 Promise,React 会暂停(suspend)当前组件的渲染,直到 Promise 完成。如果 Promise 被拒绝,组件会抛出一个异常,可以被错误边界(Error Boundary)捕获。
import { use, Suspense } from 'react';
async function fetchUser(id) {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
function UserProfile({ userId }) {
// use() 接收一个 Promise,会 suspend 组件直到数据就绪
const user = use(fetchUser(userId));
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
</div>
);
}
function App() {
return (
<Suspense fallback={<p>加载用户信息...</p>}>
<UserProfile userId={1} />
</Suspense>
);
}
工作原理:
- 当
use(fetchUser(userId))第一次执行时,传入的 Promise 还处于 pending 状态,React 就会“暂停”UserProfile组件的渲染。 - 父级的
<Suspense>会捕获这个暂停信号,显示fallback内容(加载中的 UI)。 - Promise resolve 之后,React 会自动重新渲染
UserProfile,use()返回 resolve 的值。 - 如果 Promise reject,错误会被向上传播,可以被错误边界处理。
与 useEffect 的区别:
useEffect是“先渲染,后请求”,组件总是会先渲染一次,然后再更新。这意味着你必须处理 loading 和 no data 的状态。use()是“先拿到数据,再渲染”,组件只在数据可用时才会真正渲染,不存在“无数据的中间状态”。代码更简洁,心智负担更低。
实际使用中的要点:
- 不要在渲染期间直接创建新的 Promise,否则每次渲染都会触发新的请求和无限循环。通常将 Promise 缓存在 state 或外部 store 中,或者配合框架(如 Next.js)使用。
- 可以使用
use()在条件语句中读取 Promise:
if (shouldShowDetails) {
const details = use(fetchDetails(item.id)); // 条件性地 suspend
}
这是传统 Hooks 无法做到的。
读取 Context:突破 Hooks 调用限制
use() 也可以接收一个 Context 对象(由 createContext 创建),效果等同于 useContext,但可以写在任何位置——条件分支、循环、回调函数内部,甚至普通工具函数中(只要最终在组件渲染期间被调用)。
const ThemeContext = createContext('light');
function Button({ children }) {
// 无需在顶层,可以在 return 后的 JSX 中读取
const theme = use(ThemeContext);
return <button className={theme}>{children}</button>;
}
function ConditionalReading({ show }) {
if (!show) return null;
const theme = use(ThemeContext); // 在条件分支中读取
return <div>当前主题:{theme}</div>;
}
这对于需要“按需读取”Context 的场景非常有用,例如性能敏感的大组件,只想在特定分支下访问 Context。
use() 的规则与注意事项
- 只能在组件或 Hook 内部调用(但不需要在顶层)。
- 必须在渲染期间调用(不能在事件处理器或 effect 中调用),因为它的“暂停”语义只对渲染期有意义。
- 传入的 Promise 需要是稳定的引用,否则每次渲染都会创建新的 Promise,导致组件反复 suspend 甚至死循环。通常将 Promise 存入 state 或使用
useRef保证只创建一次,或依赖框架提供的缓存机制。 - 与 Server Components 的关系:在 RSC 中,你通常直接在服务端
await数据,不需要use()。use()更适合客户端组件中需要异步读取数据且不想管理 loading 状态的场景,尤其是与 Suspense 配合。
统一消费的本质
use() 的设计统一了两种“从组件外部读取数据”的模式:
| 资源类型 | 传统 API | use() 方式 |
|---------|----------|------------|
| 同步上下文 | useContext(Theme) | use(Theme) |
| 异步数据 | useEffect + state 或第三方库 | use(promise) |
这种统一降低了 API 表面积,也为未来 React 的自动依赖追踪和编译器优化埋下了基础。React 19 的自动编译器(React Compiler)可以更好地分析和优化 use() 调用,因为它看起来就像一个普通的“读取资源”的函数,语义更清晰。
何时应该使用 use()
- 你已经有
<Suspense>来包裹组件树,想用“渲染即等待”模式简化异步数据读取。 - 需要在条件逻辑中读取 Context,而传统
useContext无法放在分支里。 - 在客户端组件中,想摆脱“必须自己管理 loading/error”的样板代码。
与第三方数据请求库的关系
像 TanStack Query (React Query) 依然是非常强大的数据管理工具,提供了缓存、去重、乐观更新等高级能力。use() 不会替代它们,而是提供了一个底层的、原生的异步读取机制。你完全可以将 TanStack Query 的查询结果包装成一个 Promise,通过 use() 进行读取,获得更好的 Suspense 集成。
const { data: user, isLoading } = useQuery('user', fetchUser);
// 依然需要处理 loading
// 如果库支持返回 Promise,结合 use() 可以改写为:
const user = use(queryClient.fetchQuery('user', fetchUser));
// 组件会自动 suspend,直到数据就绪
总之,use() 是 React 进一步拥抱异步和声明式编程的重要一步,让组件代码更加专注于“数据准备好之后的样子”。