Next.js v15 - 服务器操作以及调用原理

约定

服务器操作是在服务器上执行的异步函数。它们可以在服务器组件和客户端组件中调用,用于处理 Next.js 应用程序中的表单提交和数据修改。

服务器操作可以通过 React 的 "use server" 指令定义。你可以将该指令放在 async 函数的顶部以将该函数标记为服务器操作,或者放在单独文件的顶部以将该文件的所有导出标记为服务器操作。

typescript 复制代码
export default function Page() {
  // 服务器操作
  async function create() {
    'use server'
    // 修改数据
  }
 
  return '...'
}
typescript 复制代码
'use server'
 
export async function create() {}

应用

React 扩展了 HTML 元素,允许通过 action 属性调用服务器操作。

typescript 复制代码
export default function Page() {
  async function createInvoice(formData: FormData) {
    'use server'
 
    const rawFormData = {
      customerId: formData.get('customerId'),
      amount: formData.get('amount'),
      status: formData.get('status'),
    }
 
    // 修改数据
    // 重新验证缓存
  }
 
  return <form action={createInvoice}>...</form>
}

通常,Next.js TypeScript 插件会标记updateItemAction,因为函数通常不能在客户端-服务器边界之间序列化,序列化是指将对象转换为可以存储或传输的格式。函数包含上下文和状态信息,无法简单地转化为可以在不同环境中执行的格式。

然而,名为 action 或以 Action 结尾的 props 被假定为接收服务器操作。 这只是一个启发式方法,因为 TypeScript 插件实际上并不知道它接收的是服务器操作还是普通函数。 运行时类型检查仍然会确保你不会意外地将函数传递给客户端组件。

处理表单时你可以将服务器操作与useActionState结合使用

typescript 复制代码
export default function Page() {
const initialState: State = { message: null, errors: {} };
  const [state, formAction] = useActionState(createInvoice, initialState);
  async function createInvoice(prevState: State, formData: FormData) {
    'use server'
 
    const rawFormData = {
      customerId: formData.get('customerId'),
      amount: formData.get('amount'),
      status: formData.get('status'),
    }
 
    // 修改数据
    // 重新验证缓存
  }
 
  return <form action={formAction}>...</form>
}

传递另外参数

服务器操作将接收 userId 参数,以及表单数据:

typescript 复制代码
'use server'
 
export async function updateUser(userId, formData) {}

程序化表单提交

你可以使用 requestSubmit() 方法以编程方式触发表单提交。例如,当用户使用 ⌘ + Enter 键盘快捷键提交表单时,你可以监听 onKeyDown 事件

typescript 复制代码
'use client'
 
export function Entry() {
  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (
      (e.ctrlKey || e.metaKey) &&
      (e.key === 'Enter' || e.key === 'NumpadEnter')
    ) {
      e.preventDefault()
      e.currentTarget.form?.requestSubmit()
    }
  }
 
  return (
    <div>
      <textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
    </div>
  )
}

原理

每当你将 'use server' 添加到服务器端函数并将其导入到客户端组件时,它都会将其标记为对客户端可用(服务器的入口点)。这并不意味着函数会被序列化并通过网络发送,相反,客户端将获得该函数的 URL 字符串,客户端可以使用它通过 RPC 向服务器发送请求。这是一个 POST 请求。这是自动为你处理的,你所要做的就是包含 'use server',导入你的 server action 或将其作为 prop 传递,然后就使用它。您永远不会看到此 URL 字符串,但这就是它在后台的工作方式。

即使您在 server 组件中使用 server 操作,添加 "use server" 也很重要。假设您在服务器组件中有一个按钮,并且您希望在有人单击该按钮时使用服务器操作。您仍然需要一个 URL 字符串,因为该按钮最终会出现在客户端上,并点击该按钮。因此,只有当你在 server 操作中包含 "use server" 指令时,它才会起作用。

此外,如果你将服务器操作导入到客户端组件中,但忘记添加 "use server",它会将该函数作为代码导入到客户端中。它将不再是服务器端功能。当你添加 "use server" 时,它会让该函数保留在服务器上。

相关推荐
程序员码歌39 分钟前
【零代码AI编程实战】AI灯塔导航-总结篇
android·前端·后端
用户21411832636021 小时前
免费玩转 AI 编程!Claude Code Router + Qwen3-Code 实战教程
前端
小小愿望3 小时前
前端无法获取响应头(如 Content-Disposition)的原因与解决方案
前端·后端
小小愿望3 小时前
项目启功需要添加SKIP_PREFLIGHT_CHECK=true该怎么办?
前端
烛阴3 小时前
精简之道:TypeScript 参数属性 (Parameter Properties) 详解
前端·javascript·typescript
海上彼尚4 小时前
使用 npm-run-all2 简化你的 npm 脚本工作流
前端·npm·node.js
开发者小天4 小时前
为什么 /deep/ 现在不推荐使用?
前端·javascript·node.js
如白驹过隙5 小时前
cloudflare缓存配置
前端·缓存
excel5 小时前
JavaScript 异步编程全解析:Promise、Async/Await 与进阶技巧
前端
Jerry说前后端5 小时前
Android 组件封装实践:从解耦到架构演进
android·前端·架构