人人都会AI编程

26.1 响应式进阶 API

更新时间:2026-07-10

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 会自动重新渲染 UserProfileuse() 返回 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 进一步拥抱异步和声明式编程的重要一步,让组件代码更加专注于“数据准备好之后的样子”。