use 是一个 React API,它允许你读取 Promisecontext 的值。

const value = use(resource);

参考

🌐 Reference

use(context)

使用一个 上下文 调用 use 来读取其值。与 useContext 不同,use 可以像 if 一样在循环和条件语句中调用。

🌐 Call use with a context to read its value. Unlike useContext, use can be called within loops and conditional statements like if.

import { use } from 'react';

function Button() {
const theme = use(ThemeContext);
// ...

查看更多示例。

参数

🌐 Parameters

返回

🌐 Returns

传入上下文的上下文值,由调用组件上方最近的上下文提供者决定。如果没有提供者,则返回的值为传递给 createContextdefaultValue

🌐 The context value for the passed context, determined by the closest context provider above the calling component. If there is no provider, the returned value is the defaultValue passed to createContext.

注意事项

🌐 Caveats

  • use 必须在组件或 Hook 内部调用。
  • Server Components 中不支持读取带有 use 的上下文。

use(promise)

使用 Promise 调用 use 来读取其解析值。调用 use 的组件在 Promise 挂起期间会 暂停。尽管名称如此,use 并不是一个 Hook。与 Hook 不同,它可以像 if 一样在循环和条件语句中调用。

🌐 Call use with a Promise to read its resolved value. The component calling use suspends while the Promise is pending. Despite its name, use is not a Hook. Unlike Hooks, it can be called inside loops and conditional statements like if.

import { use } from 'react';

function MessageComponent({ messagePromise }) {
const message = use(messagePromise);
// ...

如果调用 use 的组件被封装在 Suspense 边界中,当 Promise 处于挂起状态时将显示回退内容。一旦 Promise 被解决,Suspense 回退内容将被使用 use 返回的数据渲染的组件替换。如果 Promise 被拒绝,将显示最近的 Error Boundary 的回退内容。

🌐 If the component that calls use is wrapped in a Suspense boundary, the fallback will be displayed while the Promise is pending. Once the Promise is resolved, the Suspense fallback is replaced by the rendered components using the data returned by use. If the Promise is rejected, the fallback of the nearest Error Boundary will be displayed.

查看更多示例。

参数

🌐 Parameters

  • promise:一个你想读取其已解决值的 Promise。该 Promise 必须被 缓存,以便在重新渲染时重复使用同一个实例。

返回

🌐 Returns

Promise 的解析值。

🌐 The resolved value of the Promise.

注意事项

🌐 Caveats

  • use 必须在组件或 Hook 内部调用。
  • use 不能在 try-catch 块内调用。相反,应将你的组件封装在 错误边界 中,以捕获错误并显示备用内容。
  • 传递给 use 的 Promise 必须被缓存,以便在重新渲染时重用相同的 Promise 实例。见下面的 Promise 缓存。
  • 当将一个 Promise 从服务器组件传递到客户端组件时,它的解析值必须是可序列化的

用法(上下文)

🌐 Usage (Context)

正在阅读带有 use 的上下文

🌐 Reading context with use

当将一个 context 传递给 use 时,它的工作方式类似于 useContext。虽然 useContext 必须在组件的顶层调用,但 use 可以在像 if 这样的条件语句和像 for 这样的循环中调用。

🌐 When a context is passed to use, it works similarly to useContext. While useContext must be called at the top level of your component, use can be called inside conditionals like if and loops like for.

import { use } from 'react';

function Button() {
const theme = use(ThemeContext);
// ...

use 返回你传入的 contextcontext value。为了确定上下文值,React 会搜索组件树,并找到该特定上下文上方最近的上下文提供者

要将上下文传递给 Button,请将其或其某个父组件封装到相应的上下文提供者中。

🌐 To pass context to a Button, wrap it or one of its parent components into the corresponding context provider.

function MyPage() {
return (
<ThemeContext value="dark">
<Form />
</ThemeContext>
);
}

function Form() {
// ... renders buttons inside ...
}

无论提供者和 Button 之间有多少层组件。当 Form 中的任何地方的 Button 调用 use(ThemeContext) 时,它将收到 "dark" 作为值。

🌐 It doesn’t matter how many layers of components there are between the provider and the Button. When a Button anywhere inside of Form calls use(ThemeContext), it will receive "dark" as the value.

useContext不同, use 可以像 if一样在条件语句和循环中被调用。

🌐 Unlike useContext, use can be called in conditionals and loops like if.

function HorizontalRule({ show }) {
if (show) {
const theme = use(ThemeContext);
return <hr className={theme} />;
}
return false;
}

useif 语句内部被调用,允许你有条件地从上下文中读取值。

易犯错误

useContext 一样,use(context) 总是寻找调用它的组件 上方 最近的上下文提供者。它向上搜索,并且 会考虑你从中调用 use(context) 的组件中的上下文提供者。

🌐 Like useContext, use(context) always looks for the closest context provider above the component that calls it. It searches upwards and does not consider context providers in the component from which you’re calling use(context).

import { createContext, use } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  return (
    <ThemeContext value="dark">
      <Form />
    </ThemeContext>
  )
}

function Form() {
  return (
    <Panel title="欢迎">
      <Button show={true}>Sign up</Button>
      <Button show={false}>Log in</Button>
    </Panel>
  );
}

function Panel({ title, children }) {
  const theme = use(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

function Button({ show, children }) {
  if (show) {
    const theme = use(ThemeContext);
    const className = 'button-' + theme;
    return (
      <button className={className}>
        {children}
      </button>
    );
  }
  return false
}

从上下文中读取 Promise

🌐 Reading a Promise from context

要在不通过 props 传递的情况下共享异步数据,可以将 Promise 设置为上下文值,然后使用 use(context) 读取它,并使用 use(promise) 解析它:

🌐 To share asynchronous data without prop drilling, set a Promise as a context value, then read it with use(context) and resolve it with use(promise):

import { use } from 'react';
import { UserContext } from './UserContext';

function Profile() {
const userPromise = use(UserContext);
const user = use(userPromise);
return <h1>{user.name}</h1>;
}

读取该值需要两次 use 调用,因为上下文值本身没有被等待。请参阅 在使用上下文之前 了解在使用上下文之前可以考虑的替代方案。

🌐 Reading the value requires two use calls because the context value itself isn’t awaited. See Before you use context for alternatives to consider before reaching for context.

将读取 Promise 的组件封装在 Suspense 边界中,这样只有该子树在 Promise 挂起时会暂停。有关使用 use 读取 Promises 的更多信息,请参见下文的 Usage (Promises)

🌐 Wrap the components that read the Promise in a Suspense boundary so only that subtree suspends while the Promise is pending. See Usage (Promises) below for more on reading Promises with use.

易犯错误

当这种模式与服务器组件一起使用时,重新获取 Promise 需要重新获取在上下文中设置该 Promise 的服务器组件。避免在组件树的高层设置 Promise,因为那样会不必要地重新获取应用的大部分内容。

🌐 When this pattern is used with Server Components, refetching the Promise requires refetching the Server Component that sets the Promise in context. Avoid setting the Promise in context high in the tree, since that would refetch large parts of the app unnecessarily.


用法(Promise)

🌐 Usage (Promises)

使用 use 读取 Promise

🌐 Reading a Promise with use

使用一个 Promise 调用 use 来读取其已解析的值。当 Promise 处于挂起状态时,组件将会暂停

🌐 Call use with a Promise to read its resolved value. The component will suspend while the Promise is pending.

import { use } from 'react';

function Albums({ albumsPromise }) {
const albums = use(albumsPromise);
return (
<ul>
{albums.map(album => (
<li key={album.id}>
{album.title} ({album.year})
</li>
))}
</ul>
);
}

将调用 use 的组件封装在一个 Suspense 边界中,这样当 Promise 处于挂起状态时,React 就可以显示一个后备内容。悬停组件上方最近的 Suspense 边界会显示其后备内容。一旦 Promise 解析完成,React 会使用 use 读取该值,并用渲染的组件替换后备内容。

🌐 Wrap the component that calls use in a Suspense boundary so React can show a fallback while the Promise is pending. The closest Suspense boundary above the suspending component shows its fallback. Once the Promise resolves, React reads the value with use and replaces the fallback with the rendered component.

Reading a Promise with use vs fetching in an Effect

例子 1 of 2:
正在使用 use 获取数据

🌐 Fetching data with use

在此示例中,Albums 使用缓存的 Promise 调用 use。组件在 Promise 挂起时会暂停,React 显示最近的 Suspense 回退。被拒绝的 Promise 会传播到最近的 错误边界

🌐 In this example, Albums calls use with a cached Promise. The component suspends while the Promise is pending, and React displays the nearest Suspense fallback. Rejected Promises propagate to the nearest Error Boundary.

import { use, Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { fetchData } from './data.js';

export default function App() {
  return (
    <ErrorBoundary fallback={<p>Could not fetch albums.</p>}>
      <Suspense fallback={<Loading />}>
        <Albums />
      </Suspense>
    </ErrorBoundary>
  );
}

function Albums() {
  const albums = use(fetchData('/albums'));
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}

function Loading() {
  return <h2>Loading...</h2>;
}

易犯错误

传递给 use 的承诺必须被缓存

🌐 Promises passed to use must be cached

在渲染期间创建的承诺会在每次渲染时重新创建,这会导致 React 反复显示 Suspense 回退,并阻止内容出现。

🌐 Promises created during render are recreated on every render, which causes React to show the Suspense fallback repeatedly and prevents content from appearing.

function Albums() {
// 🔴 `fetch` creates a new Promise on every render.
const albums = use(fetch('/albums'));
// ...
}

相反,可以传递来自缓存、支持 Suspense 的框架 或服务器组件的 Promise:

🌐 Instead, pass a Promise from a cache, a Suspense-enabled framework, or a Server Component:

// ✅ fetchData reads the Promise from a cache.
const albums = use(fetchData('/albums'));
深入研究

为什么每次渲染都会重新创建 Promise?

🌐 Why are Promises recreated on every render?

React不会为在挂载前暂停的渲染保留状态。每次暂停后,React都会从头重新尝试渲染,因此在渲染期间创建的任何Promise都会被重新创建。

在渲染期间,Promise 无意中被重新创建的常见方式:

🌐 Common ways a Promise can be unintentionally recreated during render:

function Albums() {
// 🔴 `fetch` creates a new Promise on every render.
const albums = use(fetch('/albums'));

// 🔴 Uncached `async` function calls create a new Promise on every render.
const albums = use((async () => {
const res = await fetch('/albums');
return res.json();
})());

// 🔴 Adding `.then` returns a new Promise on every render,
// even if `fetchData` is cached.
const albums = use(fetchData('/albums').then(res => res.json()));
// ...
}

理想情况下,Promises 应在渲染之前创建,例如在事件处理器、路由加载器或服务器组件中,并传递给调用 use 的组件。在渲染中延迟获取会推迟网络请求,并可能造成请求瀑布效应。

🌐 Ideally, Promises are created before rendering, such as in an event handler, a route loader, or a Server Component, and passed to the component that calls use. Fetching lazily in render delays network requests and can create waterfalls.

// ✅ fetchData reads the Promise from a cache.
const albums = use(fetchData('/albums'));

为客户端组件缓存 Promise

🌐 Caching Promises for Client Components

传递给客户端组件中的 use 的 Promise 必须被缓存,以便在重新渲染时重复使用相同的 Promise 实例。如果在渲染中直接创建新的 Promise,React 会在每次重新渲染时显示 Suspense 回退。

🌐 Promises passed to use in Client Components must be cached so the same Promise instance is reused across re-renders. If a new Promise is created directly in render, React will display the Suspense fallback on every re-render.

// ✅ Cache the Promise so the same one is reused across renders
let cache = new Map();

export function fetchData(url) {
if (!cache.has(url)) {
cache.set(url, getData(url));
}
return cache.get(url);
}

fetchData 函数在每次使用相同的 URL 调用时都会返回相同的 Promise。当 use 在重新渲染时接收到相同的 Promise 时,它会同步读取已解析的值,而不会挂起。

🌐 The fetchData function returns the same Promise each time it’s called with the same URL. When use receives the same Promise on a re-render, it reads the already-resolved value synchronously without suspending.

注意

你缓存 Promise 的方式取决于你在 Suspense 中使用的框架。框架通常提供内置的缓存机制。如果你不使用框架,你可以像上面那样使用一个简单的模块级缓存,或者使用一个支持 Suspense 的数据源

🌐 The way you cache Promises depends on the framework you use with Suspense. Frameworks typically provide built-in caching mechanisms. If you don’t use a framework, you can use a simple module-level cache like the one above, or a Suspense-enabled data source.

在下面的示例中,点击“重新渲染”会更新 App 中的状态并触发重新渲染。因为 fetchData 返回相同的缓存 Promise,Albums 同步读取该值,而不是再次显示 Suspense 回退内容。

🌐 In the example below, clicking “Re-render” updates state in App and triggers a re-render. Because fetchData returns the same cached Promise, Albums reads the value synchronously instead of showing the Suspense fallback again.

import { use, Suspense, useState } from 'react';
import { fetchData } from './data.js';

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        Re-render
      </button>
      <p>Render count: {count}</p>
      <Suspense fallback={<p>Loading...</p>}>
        <Albums />
      </Suspense>
    </>
  );
}

function Albums() {
  const albums = use(fetchData('/albums'));
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}

深入研究

如何实现一个承诺缓存

🌐 How to implement a promise cache

一个基本的缓存会将 Promise 按 URL 进行存储,以便在多次渲染中重用同一个实例。为了在数据已经可用时避免不必要的 Suspense 回退,你可以在 Promise 上设置 statusvalue(或者 reason)字段。React 在调用 use 时会检查这些字段:如果 status'fulfilled',它会同步读取 value 而不挂起。如果 status'rejected',它会抛出 reason。如果字段缺失或是 'pending',则会挂起。

🌐 A basic cache stores the Promise keyed by URL so the same instance is reused across renders. To also avoid unnecessary Suspense fallbacks when data is already available, you can set status and value (or reason) fields on the Promise. React checks these fields when use is called: if status is 'fulfilled', it reads value synchronously without suspending. If status is 'rejected', it throws reason. If the field is missing or 'pending', it suspends.

let cache = new Map();

function fetchData(url) {
if (!cache.has(url)) {
const promise = getData(url);
promise.status = 'pending';
promise.then(
value => {
promise.status = 'fulfilled';
promise.value = value;
},
reason => {
promise.status = 'rejected';
promise.reason = reason;
},
);
cache.set(url, promise);
}
return cache.get(url);
}

这主要对构建支持 Suspense 数据层的库作者有用。对于没有 status 字段的 Promise,React 会自己设置该字段,但如果你自己设置,可以在数据已可用时避免额外的渲染。

🌐 This is primarily useful for library authors building Suspense-compatible data layers. React will set the status field itself on Promises that don’t have it, but setting it yourself avoids an extra render when the data is already available.

这个缓存模式是重新获取数据的基础(当缓存键改变时会触发新的获取)以及悬停时预加载数据的基础(提前调用 fetchData 意味着当 use 读取它时,Promise 可能已经被解决)。

🌐 This cache pattern is the foundation for re-fetching data (where changing the cache key triggers a new fetch) and preloading data on hover (where calling fetchData early means the Promise may already be resolved by the time use reads it).

易犯错误

不要根据 Promise 是否已经完成来跳过调用 use

🌐 Don’t skip calling use based on whether a Promise is already settled.

与其他钩子不同,use 可以在条件和循环中调用——但必须始终为 Promise 本身调用。切勿直接读取 promise.statuspromise.value 来绕过 use;始终将 Promise 传递给 use,让 React 处理它。

🌐 Unlike other hooks, use can be called inside conditions and loops — but it must always be called for the Promise itself. Never read promise.status or promise.value directly to bypass use; always pass the Promise to use and let React handle it.

// 🔴 Don't bypass `use` by reading promise status directly
if (promise.status === 'fulfilled') {
return promise.value;
}
const value = use(promise);
// ✅ Pass the promise to `use` and let React track the promise
const value = use(promise);

以这种方式绕过 use 可能会破坏 React Suspense 的优化以及 React DevTools 的 Suspense 功能。你可以有条件地 use(promise),但不要根据 promise 本身有条件地 use(promise)

🌐 Bypassing use this way can break React Suspense optimizations and Suspense features for React DevTools. You can use(promise) conditionally, but don’t conditionally use(promise) based on the promise itself.


在客户端组件中重新获取数据

🌐 Re-fetching data in Client Components

要在相同的 URL 刷新数据(例如,通过“刷新”按钮),需要使缓存条目无效,并在 startTransition 内启动新的获取操作。将生成的 Promise 存储在状态中以触发重新渲染。当新的 Promise 处于挂起状态时,由于更新在 Transition 内,React 会继续显示现有内容。

🌐 To refresh data at the same URL (for example, with a “Refresh” button), invalidate the cache entry and start a new fetch inside a startTransition. Store the resulting Promise in state to trigger a re-render. While the new Promise is pending, React keeps showing the existing content because the update is inside a Transition.

function App() {
const [albumsPromise, setAlbumsPromise] = useState(fetchData('/albums'));
const [isPending, startTransition] = useTransition();

function handleRefresh() {
startTransition(() => {
setAlbumsPromise(refetchData('/albums'));
});
}
// ...
}

refetchData 清除旧的缓存条目并在相同的 URL 开始新的获取。将生成的 Promise 存储在状态中会触发 Transition 内部的重新渲染。在重新渲染时,Albums 接收到新的 Promise,而 use 在其上挂起,同时 React 继续显示旧内容。

import { Suspense, useState, useTransition } from 'react';
import { use } from 'react';
import { fetchData, refetchData } from './data.js';

export default function App() {
  const [albumsPromise, setAlbumsPromise] = useState(
    () => fetchData('/the-beatles/albums')
  );
  const [isPending, startTransition] = useTransition();

  function handleRefresh() {
    startTransition(() => {
      setAlbumsPromise(refetchData('/the-beatles/albums'));
    });
  }

  return (
    <>
      <button
        onClick={handleRefresh}
        disabled={isPending}
      >
        {isPending ? 'Refreshing...' : 'Refresh'}
      </button>
      <div style={{ opacity: isPending ? 0.6 : 1 }}>
        <Suspense fallback={<Loading />}>
          <Albums albumsPromise={albumsPromise} />
        </Suspense>
      </div>
    </>
  );
}

function Albums({ albumsPromise }) {
  const albums = use(albumsPromise);
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}

function Loading() {
  return <h2>Loading...</h2>;
}

注意

支持 Suspense 的框架通常提供自己的缓存和失效机制。上面的自定义缓存对于理解这种模式很有用,但在实际操作中,建议使用框架的数据获取解决方案。

🌐 Frameworks that support Suspense typically provide their own caching and invalidation mechanisms. The custom cache above is useful for understanding the pattern, but in practice prefer your framework’s data fetching solution.


在悬停时预加载数据

🌐 Preloading data on hover

你可以在数据需要之前开始加载数据,通过在悬停事件中调用 fetchData。由于 fetchData 会缓存 Promise,到用户点击时数据可能已经可用。如果在 use 读取时 Promise 已经解决,React 会立即渲染组件,而不会显示 Suspense 的回退内容。

🌐 You can start loading data before it’s needed by calling fetchData during a hover event. Since fetchData caches the Promise, the data may already be available by the time the user clicks. If the Promise has resolved by the time use reads it, React renders the component immediately without showing a Suspense fallback.

<button
onMouseEnter={() => fetchData(`/${id}/albums`)}
onClick={() => {
startTransition(() => {
setArtistId(id);
});
}}
>

在此示例中,将鼠标悬停在艺术家按钮上会在后台开始获取他们的专辑。如果没有先悬停,点击时会显示加载回退。尝试在点击前将鼠标悬停在某个按钮上片刻,以查看差异。

🌐 In this example, hovering over an artist button starts fetching their albums in the background. Without hovering first, clicking shows a loading fallback. Try hovering over a button for a moment before clicking to see the difference.

import { Suspense, useState, useTransition } from 'react';
import Albums from './Albums.js';
import { fetchData } from './data.js';

export default function App() {
  const [artistId, setArtistId] = useState('the-beatles');
  const [isPending, startTransition] = useTransition();

  return (
    <>
      <div>
        {['the-beatles', 'led-zeppelin', 'pink-floyd'].map(id => (
          <button
            key={id}
            onMouseEnter={() => {
              fetchData(`/${id}/albums`);
            }}
            onClick={() => {
              startTransition(() => {
                setArtistId(id);
              });
            }}
          >
            {id === 'the-beatles' ? 'The Beatles' :
             id === 'led-zeppelin' ? 'Led Zeppelin' :
             'Pink Floyd'}
          </button>
        ))}
      </div>
      <Suspense key={artistId} fallback={<Loading />}>
        <Albums artistId={artistId} />
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>Loading...</h2>;
}


从服务器到客户端的流式数据

🌐 Streaming data from server to client

可以通过将 Promise 作为属性从服务器组件传递到客户端组件来实现从服务器向客户端的数据流传输。

🌐 Data can be streamed from the server to the client by passing a Promise as a prop from a Server Component to a Client Component.

import { fetchMessage } from './lib.js';
import { Message } from './message.js';

export default function App() {
const messagePromise = fetchMessage();
return (
<Suspense fallback={<p>waiting for message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}

然后,客户端组件将其作为属性接收到的 Promise 传递给 use API。这允许客户端组件读取最初由服务器组件创建的 Promise 中的值。

🌐 The Client Component then takes the Promise it received as a prop and passes it to the use API. This allows the Client Component to read the value from the Promise that was initially created by the Server Component.

// message.js
'use client';

import { use } from 'react';

export function Message({ messagePromise }) {
const messageContent = use(messagePromise);
return <p>Here is the message: {messageContent}</p>;
}

Because Message is wrapped in a Suspense boundary, the fallback will be displayed until the Promise is resolved. When the Promise is resolved, the value will be read by the use API and the Message component will replace the Suspense fallback.

"use client";

import { use, Suspense } from "react";

function Message({ messagePromise }) {
  const messageContent = use(messagePromise);
  return <p>Here is the message: {messageContent}</p>;
}

export function MessageContainer({ messagePromise }) {
  return (
    <Suspense fallback={<p>⌛Downloading message...</p>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}

深入研究

我应该在服务器或客户端组件中解析 Promise 吗?

🌐 Should I resolve a Promise in a Server or Client Component?

如果你有一个 Promise,在某些时候你需要将其解包以读取其值。在服务器组件中,你使用 await 来解包它,在客户端组件中,你使用 use 来解包它。

🌐 If you have a Promise, at some point you need to unwrap it to read its value. You unwrap it with await in a Server Component, and with use in a Client Component.

通常,最简单的选项是在创建 Promise 的地方 await 它。服务器组件会挂起,直到数据准备好,其下的所有内容也会等待:

🌐 Usually, the simplest option is to await the Promise where you create it. The Server Component suspends until the data is ready, and everything below it waits too:

// Server Component
export default async function App() {
const messageContent = await fetchMessage();
return <Message messageContent={messageContent} />;
}

然而,你不必立即解包它。你可以将 Promise 作为 prop 传递下去,并在树的更深处解包它。读取 Promise 的组件仍会挂起,但只有该部分的树会等待数据。将该组件封装在 <Suspense> 边界内,以在页面的其他部分立即渲染的同时显示一个回退内容。

🌐 However, you don’t have to unwrap it right away. You can pass the Promise down as a prop, and unwrap it deeper in the tree. The component that reads the Promise still suspends, but only that part of the tree waits for the data. Wrap that component in a <Suspense> boundary to show a fallback while the rest of the page renders immediately.

例如,一个更深层的服务器组件可以 await 它收到的 Promise:

🌐 For example, a deeper Server Component can await the Promise it receives:

import { Suspense } from 'react';

// Server Component
export default function App() {
const messagePromise = fetchMessage();
return (
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}

// Server Component
async function Message({ messagePromise }) {
const messageContent = await messagePromise;
return <p>{messageContent}</p>;
}

或者,在一个单独的文件中,客户端组件可以使用 use 解开相同的 Promise:

🌐 Or, in a separate file, a Client Component can unwrap the same Promise with use:

// Client Component
'use client';

import { use } from 'react';

export function Message({ messagePromise }) {
const messageContent = use(messagePromise);
return <p>{messageContent}</p>;
}

在两种情况下,传递 Promise 的方式是一样的。两者都会在读取 Promise 的地方暂停,并都会解锁上方的 UI。唯一的区别是客户端组件在渲染期间不能 await,所以它们改为用 use 解包 Promise。一个常见的例子是交互式内容,如弹出框和工具提示,这些内容只在悬停或点击后才需要数据。

🌐 Passing the Promise down works the same way in both cases. Both suspend where the Promise is read, and both unblock the UI above. The only difference is that Client Components can’t await during render, so they unwrap the Promise with use instead. A common case is interactive content like popovers and tooltips, where the data is only needed after a hover or click.

请参阅 一次性显示内容 以了解在何处放置 Suspense 边界的指导。

🌐 See Revealing content together at once for guidance on where to place Suspense boundaries.


使用错误边界显示错误

🌐 Displaying an error with an Error Boundary

如果传递给 use 的 Promise 被拒绝,错误会传播到最近的 错误边界。将调用 use 的组件封装在错误边界中,以在 Promise 被拒绝时显示备用内容。

🌐 If the Promise passed to use is rejected, the error propagates to the nearest Error Boundary. Wrap the component that calls use in an Error Boundary to display a fallback when the Promise is rejected.

在下面的示例中,fetchData 在第一次尝试时拒绝,并在重试时成功。错误边界捕获该拒绝并显示带有“再试一次”按钮的回退界面。

🌐 In the example below, fetchData rejects on the first attempt and succeeds on retry. The Error Boundary catches the rejection and shows a fallback with a “Try again” button.

import { use, Suspense, useState, startTransition } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { fetchData, refetchData } from "./data.js";

export default function App() {
  const [albumsPromise, setAlbumsPromise] = useState(
    () => fetchData('/the-beatles/albums')
  );

  function handleRetry() {
    startTransition(() => {
      setAlbumsPromise(refetchData('/the-beatles/albums'));
    });
  }

  return (
    <ErrorBoundary
      resetKeys={[albumsPromise]}
      fallbackRender={() => (
        <>
          <p>⚠️ Something went wrong loading the albums.</p>
          <button onClick={handleRetry}>Try again</button>
        </>
      )}
    >
      <Suspense fallback={<p>Loading...</p>}>
        <Albums albumsPromise={albumsPromise} />
      </Suspense>
    </ErrorBoundary>
  );
}

function Albums({ albumsPromise }) {
  const albums = use(albumsPromise);
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}


故障排除

🌐 Troubleshooting

我收到一个错误:“Suspense 异常:这不是真正的错误!”

🌐 I’m getting an error: “Suspense Exception: This is not a real error!”

你正在在 try-catch 块中调用 useuse 为了与 Suspense 集成会内部抛出异常,所以不能封装在 try-catch 中。相反,应将调用 use 的组件封装在 错误边界 中以处理错误。

🌐 You are calling use inside a try-catch block. use throws internally to integrate with Suspense, so it cannot be wrapped in try-catch. Instead, wrap the component that calls use in an Error Boundary to handle errors.

function Albums({ albumsPromise }) {
try {
// ❌ Don't wrap `use` in try-catch
const albums = use(albumsPromise);
} catch (e) {
return <p>Error</p>;
}
// ...

相反,将组件封装在错误边界中:

🌐 Instead, wrap the component in an Error Boundary:

function Albums({ albumsPromise }) {
// ✅ Call `use` without try-catch
const albums = use(albumsPromise);
// ...
// ✅ Use an Error Boundary to handle errors
<ErrorBoundary fallback={<p>Error</p>}>
<Albums albumsPromise={albumsPromise} />
</ErrorBoundary>

我收到了一个警告:“一个组件被未缓存的 promise 暂停了”

🌐 I’m getting a warning: “A component was suspended by an uncached promise”

传递给 use 的 Promise 没有被缓存,因此 React 无法在重新渲染时重用它。

🌐 The Promise passed to use is not cached, so React cannot reuse it across re-renders.

当在渲染中直接调用 fetchasync 函数时,这种情况通常会发生:

🌐 This commonly happens when calling fetch or an async function directly in render:

function Albums() {
// 🔴 This creates a new Promise on every render
const albums = use(fetch('/albums'));
// ...
}

要解决此问题,请缓存 Promise,以便重复使用同一个实例:

🌐 To fix this, cache the Promise so the same instance is reused:

// ✅ fetchData returns the same Promise for the same URL
const albums = use(fetchData('/albums'));

有关更多详情,请参阅 客户端组件的缓存 Promise

🌐 See caching Promises for Client Components for more details.