约定
服务器操作是在服务器上执行的异步函数。它们可以在服务器组件和客户端组件中调用,用于处理 Next.js 应用程序中的表单提交和数据修改。
服务器操作可以通过 React 的 “use server” 指令定义。你可以将该指令放在 async 函数的顶部以将该函数标记为服务器操作,或者放在单独文件的顶部以将该文件的所有导出标记为服务器操作。
export default function Page() {
// 服务器操作
async function create() {
'use server'
// 修改数据
}
return '...'
}
'use server'
export async function create() {}
应用
React 扩展了 HTML 元素,允许通过 action 属性调用服务器操作。
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结合使用
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 参数,以及表单数据:
'use server'
export async function updateUser(userId, formData) {}
程序化表单提交
你可以使用 requestSubmit() 方法以编程方式触发表单提交。例如,当用户使用 ⌘ + Enter 键盘快捷键提交表单时,你可以监听 onKeyDown 事件
'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” 时,它会让该函数保留在服务器上。