'use client' - This feature is available in the latest Canary

Canary

仅当你是 使用 React 服务器组件 或构建与它们兼容的库时才需要 'use client'

¥'use client' is needed only if you’re using React Server Components or building a library compatible with them.

'use client' 允许你标记客户端上运行的代码。

¥'use client' lets you mark what code runs on the client.


参考

¥Reference

'use client'

在文件顶部添加 'use client' 以将模块及其传递依赖标记为客户端代码。

¥Add 'use client' at the top of a file to mark the module and its transitive dependencies as client code.

'use client';

import { useState } from 'react';
import { formatDate } from './formatters';
import Button from './button';

export default function RichTextEditor({ timestamp, text }) {
const date = formatDate(timestamp);
// ...
const editButton = <Button />;
// ...
}

当从服务器组件导入标有 'use client' 的文件时,兼容的打包器 会将模块导入视为服务器运行代码和客户端运行代码之间的边界。

¥When a file marked with 'use client' is imported from a Server Component, compatible bundlers will treat the module import as a boundary between server-run and client-run code.

由于 RichTextEditorformatDateButton 的依赖也将在客户端上进行评估,无论它们的模块是否包含 'use client' 指令。请注意,从服务器代码导入时可以在服务器上评估单个模块,从客户端代码导入时可以在客户端上评估单个模块。

¥As dependencies of RichTextEditor, formatDate and Button will also be evaluated on the client regardless of whether their modules contain a 'use client' directive. Note that a single module may be evaluated on the server when imported from server code and on the client when imported from client code.

注意事项

¥Caveats

  • 'use client' 必须位于文件的开头,位于任何导入或其他代码之上(注释可以)。它们必须用单引号或双引号编写,但不能用反引号。

    ¥'use client' must be at the very beginning of a file, above any imports or other code (comments are OK). They must be written with single or double quotes, but not backticks.

  • 当从另一个客户端渲染的模块导入 'use client' 模块时,该指令无效。

    ¥When a 'use client' module is imported from another client-rendered module, the directive has no effect.

  • 当组件模块包含 'use client' 指令时,对该组件的任何使用都保证是客户端组件。但是,即使组件没有 'use client' 指令,仍然可以在客户端上对其进行评估。

    ¥When a component module contains a 'use client' directive, any usage of that component is guaranteed to be a Client Component. However, a component can still be evaluated on the client even if it does not have a 'use client' directive.

    • 如果组件使用在具有 'use client' 指令的模块中定义,或者当它是包含 'use client' 指令的模块的传递依赖时,则将其视为客户端组件。否则,它是一个服务器组件。

      ¥A component usage is considered a Client Component if it is defined in module with 'use client' directive or when it is a transitive dependency of a module that contains a 'use client' directive. Otherwise, it is a Server Component.

  • 标记为供客户评估的代码不限于组件。属于客户端模块子树一部分的所有代码都发送到客户端并由客户端运行。

    ¥Code that is marked for client evaluation is not limited to components. All code that is a part of the Client module sub-tree is sent to and run by the client.

  • 当服务器评估的模块从 'use client' 模块导入值时,这些值必须是 React 组件或 支持的可序列化属性值 才能传递给客户端组件。任何其他用例都会引发异常。

    ¥When a server evaluated module imports values from a 'use client' module, the values must either be a React component or supported serializable prop values to be passed to a Client Component. Any other use case will throw an exception.

'use client' 如何标记客户端代码

¥How 'use client' marks client code

在 React 应用中,组件通常被分成单独的文件,或 modules

¥In a React app, components are often split into separate files, or modules.

对于使用 React Server 组件的应用,该应用默认情况下是服务器渲染的。'use client'模块依赖树 中引入了服务器-客户端边界,有效地创建了客户端模块的子树。

¥For apps that use React Server Components, the app is server-rendered by default. 'use client' introduces a server-client boundary in the module dependency tree, effectively creating a subtree of Client modules.

为了更好地说明这一点,请考虑以下 React Server Components 应用。

¥To better illustrate this, consider the following React Server Components app.

import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';

export default function App() {
  return (
    <>
      <FancyText title text="Get Inspired App" />
      <InspirationGenerator>
        <Copyright year={2004} />
      </InspirationGenerator>
    </>
  );
}

在此示例应用的模块依赖树中,InspirationGenerator.js 中的 'use client' 指令将该模块及其所有传递依赖标记为客户端模块。从 InspirationGenerator.js 开始的子树现在被标记为客户端模块。

¥In the module dependency tree of this example app, the 'use client' directive in InspirationGenerator.js marks that module and all of its transitive dependencies as Client modules. The subtree starting at InspirationGenerator.js is now marked as Client modules.

A tree graph with the top node representing the module 'App.js'. 'App.js' has three children: 'Copyright.js', 'FancyText.js', and 'InspirationGenerator.js'. 'InspirationGenerator.js' has two children: 'FancyText.js' and 'inspirations.js'. The nodes under and including 'InspirationGenerator.js' have a yellow background color to signify that this sub-graph is client-rendered due to the 'use client' directive in 'InspirationGenerator.js'.
A tree graph with the top node representing the module 'App.js'. 'App.js' has three children: 'Copyright.js', 'FancyText.js', and 'InspirationGenerator.js'. 'InspirationGenerator.js' has two children: 'FancyText.js' and 'inspirations.js'. The nodes under and including 'InspirationGenerator.js' have a yellow background color to signify that this sub-graph is client-rendered due to the 'use client' directive in 'InspirationGenerator.js'.
'use client' 对 React Server Components 应用的模块依赖树进行分段,将 InspirationGenerator.js 及其所有依赖标记为客户端渲染。

在渲染期间,框架将服务器渲染根组件并继续执行 渲染树,选择不评估从客户端标记的代码导入的任何代码。

¥During render, the framework will server-render the root component and continue through the render tree, opting-out of evaluating any code imported from client-marked code.

然后,渲染树的服务器渲染部分被发送到客户端。下载客户端代码后,客户端将完成树的其余部分的渲染。

¥The server-rendered portion of the render tree is then sent to the client. The client, with its client code downloaded, then completes rendering the rest of the tree.

A tree graph where each node represents a component and its children as child components. The top-level node is labelled 'App' and it has two child components 'InspirationGenerator' and 'FancyText'. 'InspirationGenerator' has two child components, 'FancyText' and 'Copyright'. Both 'InspirationGenerator' and its child component 'FancyText' are marked to be client-rendered.
A tree graph where each node represents a component and its children as child components. The top-level node is labelled 'App' and it has two child components 'InspirationGenerator' and 'FancyText'. 'InspirationGenerator' has two child components, 'FancyText' and 'Copyright'. Both 'InspirationGenerator' and its child component 'FancyText' are marked to be client-rendered.
React Server Components 应用的渲染树。 InspirationGenerator 及其子组件 FancyText 是从客户端标记代码导出的组件,并被视为客户端组件。

我们引入以下定义:

¥We introduce the following definitions:

  • 客户端组件是渲染树中在客户端上渲染的组件。

    ¥Client Components are components in a render tree that are rendered on the client.

  • 服务器组件是渲染树中在服务器上渲染的组件。

    ¥Server Components are components in a render tree that are rendered on the server.

通过示例应用,AppFancyTextCopyright 都是服务器渲染的并被视为服务器组件。由于 InspirationGenerator.js 及其传递依赖被标记为客户端代码,因此组件 InspirationGenerator 及其子组件 FancyText 是客户端组件。

¥Working through the example app, App, FancyText and Copyright are all server-rendered and considered Server Components. As InspirationGenerator.js and its transitive dependencies are marked as client code, the component InspirationGenerator and its child component FancyText are Client Components.

深入研究

FancyText 为什么既是服务器组件又是客户端组件?

¥How is FancyText both a Server and a Client Component?

根据上面的定义,组件 FancyText 既是服务器组件又是客户端组件,怎么可能呢?

¥By the above definitions, the component FancyText is both a Server and Client Component, how can that be?

首先,我们要澄清一下,术语““component””并不是很精确。以下是 “component” 的两种理解方式:

¥First, let’s clarify that the term “component” is not very precise. Here are just two ways “component” can be understood:

  1. “component” 可以指组件定义。在大多数情况下,这将是一个函数。

    ¥A “component” can refer to a component definition. In most cases this will be a function.

// This is a definition of a component
function MyComponent() {
return <p>My Component</p>
}
  1. ”component” 还可以指其定义的组件用法。

    ¥A “component” can also refer to a component usage of its definition.

import MyComponent from './MyComponent';

function App() {
// This is a usage of a component
return <MyComponent />;
}

通常,在解释概念时,不精确性并不重要,但在这种情况下却很重要。

¥Often, the imprecision is not important when explaining concepts, but in this case it is.

当我们谈论服务器或客户端组件时,我们指的是组件的使用。

¥When we talk about Server or Client Components, we are referring to component usages.

  • 如果组件是在带有 'use client' 指令的模块中定义的,或者组件是在客户端组件中导入和调用的,则该组件用法是客户端组件。

    ¥If the component is defined in a module with a 'use client' directive, or the component is imported and called in a Client Component, then the component usage is a Client Component.

  • 否则,该组件使用是服务器组件。

    ¥Otherwise, the component usage is a Server Component.

A tree graph where each node represents a component and its children as child components. The top-level node is labelled 'App' and it has two child components 'InspirationGenerator' and 'FancyText'. 'InspirationGenerator' has two child components, 'FancyText' and 'Copyright'. Both 'InspirationGenerator' and its child component 'FancyText' are marked to be client-rendered.
A tree graph where each node represents a component and its children as child components. The top-level node is labelled 'App' and it has two child components 'InspirationGenerator' and 'FancyText'. 'InspirationGenerator' has two child components, 'FancyText' and 'Copyright'. Both 'InspirationGenerator' and its child component 'FancyText' are marked to be client-rendered.
渲染树说明了组件的用法。

回到 FancyText 的问题,我们看到组件定义没有 'use client' 指令,它有两种用法。

¥Back to the question of FancyText, we see that the component definition does not have a 'use client' directive and it has two usages.

使用 FancyText 作为 App 的子组件,标志着该用法是服务器组件。当 FancyTextInspirationGenerator 下导入并调用时,FancyText 的用法是客户端组件,因为 InspirationGenerator 包含 'use client' 指令。

¥The usage of FancyText as a child of App, marks that usage as a Server Component. When FancyText is imported and called under InspirationGenerator, that usage of FancyText is a Client Component as InspirationGenerator contains a 'use client' directive.

这意味着 FancyText 的组件定义将在服务器上评估,并由客户端下载以渲染其客户端组件使用情况。

¥This means that the component definition for FancyText will both be evaluated on the server and also downloaded by the client to render its Client Component usage.

深入研究

¥Why is Copyright a Server Component?

由于 Copyright 渲染为客户端组件 InspirationGenerator 的子组件,因此你可能会惊讶它是服务器组件。

¥Because Copyright is rendered as a child of the Client Component InspirationGenerator, you might be surprised that it is a Server Component.

回想一下,'use client' 在模块依赖树上定义服务器和客户端代码之间的边界,而不是渲染树。

¥Recall that 'use client' defines the boundary between server and client code on the module dependency tree, not the render tree.

A tree graph with the top node representing the module 'App.js'. 'App.js' has three children: 'Copyright.js', 'FancyText.js', and 'InspirationGenerator.js'. 'InspirationGenerator.js' has two children: 'FancyText.js' and 'inspirations.js'. The nodes under and including 'InspirationGenerator.js' have a yellow background color to signify that this sub-graph is client-rendered due to the 'use client' directive in 'InspirationGenerator.js'.
A tree graph with the top node representing the module 'App.js'. 'App.js' has three children: 'Copyright.js', 'FancyText.js', and 'InspirationGenerator.js'. 'InspirationGenerator.js' has two children: 'FancyText.js' and 'inspirations.js'. The nodes under and including 'InspirationGenerator.js' have a yellow background color to signify that this sub-graph is client-rendered due to the 'use client' directive in 'InspirationGenerator.js'.
'use client' 定义了模块依赖树上服务器和客户端代码之间的边界。

在模块依赖树中,我们看到 App.jsCopyright.js 模块导入并调用 Copyright。由于 Copyright.js 不包含 'use client' 指令,因此组件使用情况将在服务器上渲染。App 在服务器上渲染,因为它是根组件。

¥In the module dependency tree, we see that App.js imports and calls Copyright from the Copyright.js module. As Copyright.js does not contain a 'use client' directive, the component usage is rendered on the server. App is rendered on the server as it is the root component.

客户端组件可以渲染服务器组件,因为你可以将 JSX 作为属性传递。在这种情况下,InspirationGenerator 接收 Copyright 作为 children。然而,InspirationGenerator 模块从不直接导入 Copyright 模块,也不会调用该组件,所有这些都是由 App 完成的。事实上,Copyright 组件在 InspirationGenerator 开始渲染之前就已经完全执行了。

¥Client Components can render Server Components because you can pass JSX as props. In this case, InspirationGenerator receives Copyright as children. However, the InspirationGenerator module never directly imports the Copyright module nor calls the component, all of that is done by App. In fact, the Copyright component is fully executed before InspirationGenerator starts rendering.

结论是组件之间的父子渲染关系不能保证相同的渲染环境。

¥The takeaway is that a parent-child render relationship between components does not guarantee the same render environment.

何时使用 'use client'

¥When to use 'use client'

使用 'use client',你可以确定组件何时是客户端组件。由于服务器组件是默认的,这里简要概述了服务器组件的优点和限制,以确定何时需要将某些内容标记为客户端渲染。

¥With 'use client', you can determine when components are Client Components. As Server Components are default, here is a brief overview of the advantages and limitations to Server Components to determine when you need to mark something as client rendered.

为了简单起见,我们讨论服务器组件,但相同的原则适用于服务器运行的应用中的所有代码。

¥For simplicity, we talk about Server Components, but the same principles apply to all code in your app that is server run.

服务器组件的优点

¥Advantages of Server Components

  • 服务器组件可以减少客户端发送和运行的代码量。只有客户端模块由客户端打包和评估。

    ¥Server Components can reduce the amount of code sent and run by the client. Only Client modules are bundled and evaluated by the client.

  • 服务器组件受益于在服务器上运行。他们可以访问本地文件系统,并且可能会遇到数据获取和网络请求的低延迟。

    ¥Server Components benefit from running on the server. They can access the local filesystem and may experience low latency for data fetches and network requests.

服务器组件的限制

¥Limitations of Server Components

  • 服务器组件无法支持交互,因为事件处理程序必须由客户端注册和触发。

    ¥Server Components cannot support interaction as event handlers must be registered and triggered by a client.

    • 例如,像 onClick 这样的事件处理程序只能在客户端组件中定义。

      ¥For example, event handlers like onClick can only be defined in Client Components.

  • 服务器组件无法使用大多数 Hook。

    ¥Server Components cannot use most Hooks.

    • 当渲染服务器组件时,它们的输出本质上是供客户端渲染的组件列表。服务器组件在渲染后不会保留在内存中,并且不能拥有自己的状态。

      ¥When Server Components are rendered, their output is essentially a list of components for the client to render. Server Components do not persist in memory after render and cannot have their own state.

服务器组件返回的可序列化类型

¥Serializable types returned by Server Components

与任何 React 应用一样,父组件将数据传递给子组件。由于它们在不同的环境中渲染,因此将数据从服务器组件传递到客户端组件需要额外考虑。

¥As in any React app, parent components pass data to child components. As they are rendered in different environments, passing data from a Server Component to a Client Component requires extra consideration.

从服务器组件传递到客户端组件的属性值必须是可序列化的。

¥Prop values passed from a Server Component to Client Component must be serializable.

可序列化的属性包括:

¥Serializable props include:

值得注意的是,这些不受支持:

¥Notably, these are not supported:

  • 函数 不是从客户端标记的模块导出或标记有 'use server'

    ¥Functions that are not exported from client-marked modules or marked with 'use server'

  • ¥Classes

  • 作为任何类(除了提到的内置类之外)实例的对象或具有 空原型 的对象

    ¥Objects that are instances of any class (other than the built-ins mentioned) or objects with a null prototype

  • 未在全局注册的符号,例如。Symbol('my new symbol')

    ¥Symbols not registered globally, ex. Symbol('my new symbol')

用法

¥Usage

具有交互性和状态的构建

¥Building with interactivity and state

'use client';

import { useState } from 'react';

export default function Counter({initialValue = 0}) {
  const [countValue, setCountValue] = useState(initialValue);
  const increment = () => setCountValue(countValue + 1);
  const decrement = () => setCountValue(countValue - 1);
  return (
    <>
      <h2>Count Value: {countValue}</h2>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </>
  );
}

由于 Counter 需要 useState 钩子和事件处理程序来递增或递减值,因此该组件必须是客户端组件,并且需要在顶部有一个 'use client' 指令。

¥As Counter requires both the useState Hook and event handlers to increment or decrement the value, this component must be a Client Component and will require a 'use client' directive at the top.

相反,无需交互即可渲染 UI 的组件不需要是客户端组件。

¥In contrast, a component that renders UI without interaction will not need to be a Client Component.

import { readFile } from 'node:fs/promises';
import Counter from './Counter';

export default async function CounterContainer() {
const initialValue = await readFile('/path/to/counter_value');
return <Counter initialValue={initialValue} />
}

例如,Counter 的父组件 CounterContainer 不需要 'use client',因为它不是交互式的并且不使用状态。此外,CounterContainer 必须是服务器组件,因为它从服务器上的本地文件系统读取,这只能在服务器组件中实现。

¥For example, Counter’s parent component, CounterContainer, does not require 'use client' as it is not interactive and does not use state. In addition, CounterContainer must be a Server Component as it reads from the local file system on the server, which is possible only in a Server Component.

还有一些组件不使用任何服务器或仅限客户端的功能,并且可以不知道它们的渲染位置。在我们前面的示例中,FancyText 就是这样的组件之一。

¥There are also components that don’t use any server or client-only features and can be agnostic to where they render. In our earlier example, FancyText is one such component.

export default function FancyText({title, text}) {
return title
? <h1 className='fancy title'>{text}</h1>
: <h3 className='fancy cursive'>{text}</h3>
}

在本例中,我们不添加 'use client' 指令,导致 FancyText 的输出(而不是其源代码)在从服务器组件引用时发送到浏览器。如前面的 Inspirations 应用示例所示,FancyText 既可用作服务器组件,也可用作客户端组件,具体取决于导入和使用的位置。

¥In this case, we don’t add the 'use client' directive, resulting in FancyText’s output (rather than its source code) to be sent to the browser when referenced from a Server Component. As demonstrated in the earlier Inspirations app example, FancyText is used as both a Server or Client Component, depending on where it is imported and used.

但是,如果 FancyText 的 HTML 输出相对于其源代码(包括依赖)较大,则强制其始终为客户端组件可能会更有效。返回长 SVG 路径字符串的组件是强制组件成为客户端组件可能更有效的一种情况。

¥But if FancyText’s HTML output was large relative to its source code (including dependencies), it might be more efficient to force it to always be a Client Component. Components that return a long SVG path string are one case where it may be more efficient to force a component to be a Client Component.

使用客户端 API

¥Using client APIs

你的 React 应用可能会使用 others 中特定于客户端的 API,例如用于 Web 存储、音频和视频操作以及设备硬件的浏览器 API。

¥Your React app may use client-specific APIs, such as the browser’s APIs for web storage, audio and video manipulation, and device hardware, among others.

在此示例中,组件使用 DOM API 来操作 canvas 元素。由于这些 API 仅在浏览器中可用,因此必须将其标记为客户端组件。

¥In this example, the component uses DOM APIs to manipulate a canvas element. Since those APIs are only available in the browser, it must be marked as a Client Component.

'use client';

import {useRef, useEffect} from 'react';

export default function Circle() {
const ref = useRef(null);
useLayoutEffect(() => {
const canvas = ref.current;
const context = canvas.getContext('2d');
context.reset();
context.beginPath();
context.arc(100, 75, 50, 0, 2 * Math.PI);
context.stroke();
});
return <canvas ref={ref} />;
}

使用第三方库

¥Using third-party libraries

通常在 React 应用中,你将利用第三方库来处理常见的 UI 模式或逻辑。

¥Often in a React app, you’ll leverage third-party libraries to handle common UI patterns or logic.

这些库可能依赖于组件钩子或客户端 API。使用以下任何 React API 的第三方组件必须在客户端上运行:

¥These libraries may rely on component Hooks or client APIs. Third-party components that use any of the following React APIs must run on the client:

如果这些库已更新为与 React 服务器组件兼容,那么它们将已经包含自己的 'use client' 标记,允许你直接从服务器组件使用它们。如果库尚未更新,或者组件需要只能在客户端指定的事件处理程序之类的属性,则你可能需要在第三方客户端组件和服务器组件之间添加自己的客户端组件文件,其中 你想使用它。

¥If these libraries have been updated to be compatible with React Server Components, then they will already include 'use client' markers of their own, allowing you to use them directly from your Server Components. If a library hasn’t been updated, or if a component needs props like event handlers that can only be specified on the client, you may need to add your own Client Component file in between the third-party Client Component and your Server Component where you’d like to use it.


React 中文网 - 粤ICP备13048890号