知海

'use server' 指令

ReactAPI 参考:服务器组件与指令

'use server' 指令

本 API 适用于 React 服务器组件

'use server' 用于标记可从客户端代码调用的服务器函数。


参考 {#reference}

'use server' {#use-server}

在异步函数体的顶部添加 'use server',即可将该函数标记为可由客户端调用。我们称这些函数为服务器函数(Server Action)

js {2} 复制代码
async function addToCart(data) {
  'use server';
  // ...
}

当客户端调用服务器函数时,它会向服务器发起网络请求,并携带所有参数的序列化副本。如果服务器函数返回一个值,该值会被序列化并返回给客户端。

也可以将 'use server' 指令添加到文件顶部,而不必逐个标记函数。文件顶部的指令会将所有导出都标记为服务器函数,可在任何地方(包括客户端代码)导入和使用。

注意事项 {#caveats}

  • 'use server' 必须位于函数体或模块的顶部,且在任何导入或其他代码之前(代码顶部的注释是允许的)。它必须使用单引号或双引号编写,不能使用反引号。
  • 'use server' 只能在服务器端文件中使用。生成的服务器函数可以通过 props 传递给客户端组件。请参阅支持的可序列化参数和返回值类型
  • 要在客户端代码中导入服务器函数,必须在模块级别使用该指令。
  • 由于底层的网络调用始终是异步的,'use server' 只能用于异步函数。
  • 始终将服务器函数的参数视为不受信任的输入,并确保对任何数据修改操作进行权限校验。请参阅安全考虑
  • 服务器函数应在 transition 中调用。传递给 <form action>formAction 的服务器函数会被自动包装在 transition 中。
  • 服务器函数专为更新服务器端状态的变更操作而设计,不建议用于数据获取。因此,实现服务器函数的框架通常一次只处理一个 Action,且没有对返回值进行缓存的能力。

安全考虑 {#security}

服务器函数的参数完全由客户端控制。出于安全考虑,请始终将它们视为不受信任的输入,并根据需要验证和转义参数。

在任何服务器函数中,请确保校验当前登录用户是否被允许执行该操作。

正在进行的工作

为防止敏感数据从服务器函数中被发送到客户端,React 提供了实验性的污染(taint)API,用于阻止唯一值和对象被传递给客户端代码。

请参阅 experimental_taintUniqueValueexperimental_taintObjectReference 了解更多。

可序列化参数和返回值 {#serializable-parameters-and-return-values}

当客户端代码通过网络调用服务器函数时,传递的任何参数都必须是可序列化的。

服务器函数参数支持以下类型:

特别注意,以下类型不受支持

  • React 元素或 JSX
  • 函数,包括组件函数以及其他并非服务器函数的函数
  • 任何类实例(上面列出的内置类除外),以及原型为 null 的对象
  • 未在全局注册的符号,例如 Symbol('my new symbol')
  • 事件处理程序中的事件对象

服务器函数支持的可序列化返回值,与客户端边界组件的可序列化 props 相同。


用法 {#usage}

表单中的服务器函数 {#server-actions-in-forms}

服务器函数最常见的用途是调用会变更数据的函数。在浏览器中,HTML form 元素 是用户提交变更的传统方式。借助 React 服务器组件,React 为表单中的服务器函数提供了一流支持。

下面是一个允许用户请求用户名的表单:

js {3} 复制代码
// App.js

async function requestUsername(formData) {
  'use server';
  const username = formData.get('username');
  // ...
}

export default function App() {
  return (
    <form action={requestUsername}>
      <input type="text" name="username" />
      <button type="submit">请求</button>
    </form>
  );
}

在此示例中,requestUsername 是一个传递给 <form> 的服务器函数。当用户提交此表单时,会向服务器函数 requestUsername 发起网络请求。当表单中的服务器函数被调用时,React 会将 FormData 作为第一个参数传给该函数。

通过将服务器函数传递给表单的 action,React 可以对表单进行逐步增强,这意味着表单可以在 JavaScript 包加载完成之前就实现提交。

处理表单中的返回值 {#handling-return-values}

在用户名请求表单中,可能会出现用户名不可用的情况,因此 requestUsername 应该告诉我们它是否失败。

可以使用 useActionState,根据服务器函数的返回结果来更新 UI,并支持逐步增强。

js 复制代码
// requestUsername.js
'use server';

export default async function requestUsername(formData) {
  const username = formData.get('username');
  if (canRequest(username)) {
    // ...
    return '成功';
  }
  return '失败';
}
js {4,8} 复制代码
// UsernameForm.js
'use client';

import { useActionState } from 'react';
import requestUsername from './requestUsername';

function UsernameForm() {
  const [returnValue, action] = useActionState(requestUsername, null, 'n/a');

  return (
    <>
      <form action={action}>
        <input type="text" name="username" />
        <button type="submit">请求</button>
      </form>
      <p>最后一次提交请求的返回值:{returnValue}</p>
    </>
  );
}

请注意,与大多数 Hook 一样,useActionState 只能在客户端代码中调用。

<form> 之外调用服务器函数 {#calling-a-server-action-outside-of-form}

服务器函数是暴露在服务器上的端点,可以在客户端代码的任何位置调用。

<form> 之外使用服务器函数时,请使用 transition,这样可以显示加载指示器、实现乐观状态更新,并处理意外错误。在表单中,服务器函数会被自动包装在 transition 中。

js {9-12} 复制代码
import incrementLike from './actions';
import { useState, useTransition } from 'react';

function LikeButton() {
  const [isPending, startTransition] = useTransition();
  const [likeCount, setLikeCount] = useState(0);

  const onClick = () => {
    startTransition(async () => {
      const currentCount = await incrementLike();
      setLikeCount(currentCount);
    });
  };

  return (
    <>
      <p>点赞数量:{likeCount}</p>
      <button onClick={onClick} disabled={isPending}>点赞</button>
    </>
  );
}
js 复制代码
// actions.js
'use server';

let likeCount = 0;
export default async function incrementLike() {
  likeCount++;
  return likeCount;
}

在调用处使用 await 获取返回的 Promise,即可读取服务器函数的返回值。

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。