参考
use(resource)
在组件中调用 use 来读取像 Promise 或 context 这样的资源的值。
import { use } from 'react';
function MessageComponent({ messagePromise }) {
const message = use(messagePromise);
const theme = use(ThemeContext);
// ...与 React Hooks 不同,use 可以在循环和条件语句(如 if)中调用。与 React Hooks 一样,调用 use 的函数必须是一个组件或 Hook。
当传入 Promise 时,use API 会与 Suspense 和 Error Boundaries 集成。调用 use 的组件在传给 use 的 Promise 处于 pending 状态时会 挂起。如果调用 use 的组件被包裹在 Suspense 边界中,则会显示 fallback。一旦 Promise 被解决,Suspense 的 fallback 会被使用 use API 返回的数据渲染出的组件所替换。如果传给 use 的 Promise 被拒绝,则会显示最近的 Error Boundary 的 fallback。
参数
返回值
use API 返回从资源中读取到的值,例如 Promise 或 context 已解决后的值。
注意事项
useAPI 必须在组件或 Hook 内部调用。- 在 Server Component 中获取数据时,优先使用
async和await,而不是use。async和await会从await被调用的位置继续渲染,而use会在数据解决后重新渲染组件。 - 更推荐在 Server Components 中创建 Promise 并将其传递给 Client Components,而不是在 Client Components 中创建 Promise。Client Components 中创建的 Promise 会在每次渲染时重新创建。从 Server Component 传递给 Client Component 的 Promise 在重新渲染之间是稳定的。查看此示例。
用法
使用 use 读取 context
当将 context 传给 use 时,其工作方式类似于 useContext。useContext 必须在组件顶层调用,而 use 可以在 if 这样的条件语句和 for 这样的循环中调用。与 useContext 相比,更推荐使用 use,因为它更灵活。
import { use } from 'react';
function Button() {
const theme = use(ThemeContext);
// ...use 会为你传入的 context 返回 context 值。为了确定 context 值,React 会搜索组件树,并找到该特定 context 的最近的、位于上方的 context 提供者。
要向 Button 传递 context,请将它或它的父组件包裹在相应的 context provider 中。
function MyPage() {
return (
<ThemeContext value="dark">
<Form />
</ThemeContext>
);
}
function Form() {
// ... 在内部渲染按钮 ...
}Provider 和 Button 之间有多少层组件都没有关系。当 Form 内部的任意位置的 Button 调用 use(ThemeContext) 时,它都会接收到 "dark" 作为值。
与 useContext 不同,use 可以在 if 这样的条件语句和循环中调用。
function HorizontalRule({ show }) {
if (show) {
const theme = use(ThemeContext);
return <hr className={theme} />;
}
return false;
}use 是在 if 语句内部调用的,这使你能够有条件地从 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="Welcome"> <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 作为 prop 从 Server Component 传递给 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>
);
}然后 Client Component 会接收 它作为 prop 收到的 Promise,并将其传给 use API。这使得 Client Component 能够读取最初由 Server Component 创建的 Promise 的值。
// message.js
'use client';
import { use } from 'react';
export function Message({ messagePromise }) {
const messageContent = use(messagePromise);
return <p>这里是消息:{messageContent}</p>;
}由于 Message 被 Suspense 包裹,因此在 Promise 解决之前会显示 fallback。Promise 解决后,use API 会读取该值,而 Message 组件会替换 Suspense fallback。
"use client"; import { use, Suspense } from "react"; function Message({ messagePromise }) { const messageContent = use(messagePromise); return <p>这里是消息:{messageContent}</p>; } export function MessageContainer({ messagePromise }) { return ( <Suspense fallback={<p>⌛正在下载消息...</p>}> <Message messagePromise={messagePromise} /> </Suspense> ); }
Deep Dive
Promise 可以从 Server Component 传递到 Client Component,并在 Client Component 中通过 use API 解析。你也可以在 Server Component 中使用 await 解析 Promise,然后将所需数据作为 prop 传给 Client Component。
export default async function App() {
const messageContent = await fetchMessage();
return <Message messageContent={messageContent} />
}但是,在 Server Component 中使用 await 会阻塞其渲染,直到 await 语句完成。将 Promise 从 Server Component 传递给 Client Component 可以避免 Promise 阻塞 Server Component 的渲染。
处理被拒绝的 Promise
在某些情况下,传给 use 的 Promise 可能会被拒绝。你可以通过以下两种方式处理被拒绝的 Promise:
使用 Error Boundary 向用户显示错误
如果你希望在 Promise 被拒绝时向用户显示错误,可以使用 Error Boundary。要使用 Error Boundary,请将你调用 use API 的组件包裹在 Error Boundary 中。如果传给 use 的 Promise 被拒绝,则会显示 Error Boundary 的 fallback。
"use client"; import { use, Suspense } from "react"; import { ErrorBoundary } from "react-error-boundary"; export function MessageContainer({ messagePromise }) { return ( <ErrorBoundary fallback={<p>⚠️出了点问题</p>}> <Suspense fallback={<p>⌛正在下载消息...</p>}> <Message messagePromise={messagePromise} /> </Suspense> </ErrorBoundary> ); } function Message({ messagePromise }) { const content = use(messagePromise); return <p>这里是消息:{content}</p>; }
使用 Promise.catch 提供一个替代值
如果你希望在传给 use 的 Promise 被拒绝时提供一个替代值,可以使用 Promise 的 catch 方法。
import { Message } from './message.js';
export default function App() {
const messagePromise = new Promise((resolve, reject) => {
reject();
}).catch(() => {
return "未找到新消息。";
});
return (
<Suspense fallback={<p>waiting for message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}要使用 Promise 的 catch 方法,请在 Promise 对象上调用 catch。catch 接收一个参数:一个以错误消息作为参数的函数。传给 catch 的函数所 返回的任何内容,都将作为 Promise 的已解决值使用。
故障排查
“Suspense 异常:这不是真正的错误!”
你要么是在 React 组件或 Hook 函数之外调用了 use,要么是在 try–catch 块中调用了 use。如果你是在 try–catch 块中调用 use,请将你的组件包裹在 Error Boundary 中,或者调用 Promise 的 catch 来捕获错误,并用另一个值来解析该 Promise。查看这些示例。
如果你是在 React 组件或 Hook 函数之外调用了 use,请将 use 调用移动到 React 组件或 Hook 函数中。
function MessageComponent({messagePromise}) {
function download() {
// ❌ 调用 `use` 的函数不是组件或 Hook
const message = use(messagePromise);
// ...相反,请在任何组件闭包之外调用 use,也就是在调用 use 的函数本身是组件或 Hook 的地方。
function MessageComponent({messagePromise}) {
// ✅ `use` 正在从组件中被调用。
const message = use(messagePromise);
// ...