Next.js第十九章(服务器函数)

服务器函数(Server Actions)

服务器函数指的是可以是服务器组件处理表单的提交,无需手动编写API接口,并且还支持数据的验证,以及状态管理等。

核心原理

是因为React扩展了原生HTMLform表单,允许通过action属性直接绑定server action函数,当表单提交后,函数会自动接受原生的FormData数据。

基本用法

我们先回顾一下传统的表单提交方式:

sequenceDiagram participant U as 用户 participant F as 前端表单 participant A as API路由(route.ts) participant D as 数据库 U->>F: 1. 填写表单数据
{name: '张三', age: 18} U->>F: 2. 点击提交按钮 F->>A: 3. fetch POST请求
/api/xxx A->>A: 4. 解析请求数据 A->>D: 5. 插入数据
db.insert('user') D-->>A: 6. 返回结果 A-->>F: 7. 响应 {code: 1, message: '成功'} F-->>U: 8. 显示结果

那么来看一下服务器函数的用法:

sequenceDiagram participant User as 用户 participant Form as 表单组件 participant ServerAction as handleLogin
(服务器函数) participant DB as 数据库 User->>Form: 填写用户名和密码 User->>Form: 点击登录按钮(type="submit") Form->>ServerAction: 提交 FormData(action属性) Note over ServerAction: 'use server'
在服务器端执行 ServerAction->>ServerAction: 获取 username ServerAction->>ServerAction: 获取 password ServerAction->>ServerAction: 转换所有数据为对象 ServerAction->>DB: 直接操作数据库
(无需API接口) DB-->>ServerAction: 登录成功

src/app/login/page.tsx

tsx 复制代码
export default function Login() {
    async function handleLogin(formData: FormData) {
        'use server'
        const username = formData.get('username') //接受单个参数
        const password = formData.get('password') //接受单个数据
        const form = Object.fromEntries(formData) //接受所有数据 {username: '张三', password: '123456'}
        //可以直接操作数据库,这样就无需编写API接口了 哇哦太方便了
    }
    return (
        <div>
            <h1>登录页面</h1>
            <div className="flex flex-col gap-2 w-[300px] mx-auto mt-30">
                <form action={handleLogin} className="flex flex-col gap-2">
                    <input className="border border-gray-300 rounded-md p-2" type="text" name="username" placeholder="用户名" />
                    <input className="border border-gray-300 rounded-md p-2" type="password" name="password" placeholder="密码" />
                    <button type="submit" className="bg-blue-500 text-white p-2 rounded-md">登录</button>
                </form>
            </div>
        </div>
    )
}

额外参数

目前只能携带固定参数例如 username password,无法携带其他参数。

tsx 复制代码
<form action={handleLogin}>
    <input type="text" name="username" placeholder="用户名" />
    <input type="password" name="password" placeholder="密码" />
    <button type="submit">登录</button>
</form>

那么我想携带ID或者其他自定义参数怎么做?

我们需要使用bind方法来进行参数扩展,这样在函数内部就可以接收到ID参数。

tsx 复制代码
export default function Login() {
                              //接受id参数
    async function handleLogin(id: number,formData: FormData) {
        'use server'
        const username = formData.get('username')
        const password = formData.get('password')
        const form = Object.fromEntries(formData)
        console.log(username, password,form,id)
    }
    const userFunction = handleLogin.bind(null,1) //绑定id参数
    return (
        <div>
            <h1>登录页面</h1>
            <div className="flex flex-col gap-2 w-[300px] mx-auto mt-30">
                        {/*使用新的函数绑定id参数 userFunction*/}
                <form action={userFunction} className="flex flex-col gap-2">
                    <input className="border border-gray-300 rounded-md p-2" type="text" name="username" placeholder="用户名" />
                    <input className="border border-gray-300 rounded-md p-2" type="password" name="password" placeholder="密码" />
                    <button type="submit" className="bg-blue-500 text-white p-2 rounded-md">登录</button>
                </form>
            </div>
        </div>
    )
}

参数校验(zod) + 读取状态

zod是一个目前非常流行的数据验证库,可以让我们在服务器端进行数据验证,避免用户输入非法数据。

bash 复制代码
npm i zod

src/app/lib/login/actions.ts

tsx 复制代码
'use server'
import { z } from "zod"
const loginSchema = z.object({
    username: z.string().min(6, '用户名不能少于6位'), //zod基本用法表示这是一个字符串,并且不能少于6位
    password: z.string().min(6, '密码不能少于6位') //zod基本用法表示这是一个字符串,并且不能少于6位
})

export async function handleLogin(_prevState: any, formData: FormData) {
    const result = loginSchema.safeParse(Object.fromEntries(formData)) //调用zod的safeParse方法进行校验
    
    if (!result.success) {
        const errorMessage = z.treeifyError(result.error).properties; //调用zod的treeifyError方法将错误信息转换为对象
        let str = ''
        Object.entries(errorMessage!).forEach(([_key, value]) => {
            value.errors.forEach((error: any) => {
                str += error + '\n' //将错误信息拼接成字符串
            })
        })
        return { message: str} //返回错误信息
    }
    //校验成功,进行数据库操作逻辑
    return { message: '登录成功' } //返回成功信息
}

src/app/login/page.tsx

如果要读取状态需要使用React19的useActionState hook,这个hook必须在客户端组件中使用。所以需要增加'use client'声明这是一个客户端组件。

参数

useActionState hook接受三个参数:

  • fn: 表单提交时触发的函数,接收上一次的 state(首次为 initialState)作为第一个参数,其余参数为表单参数
  • initialState: state 的初始值,可以是任何可序列化的值
  • permalink(可选): 表单提交后跳转的 URL,用于 JavaScript 加载前的渐进式增强

返回值:

  • state: 当前状态,初始值为 initialState,之后为 action 的返回值
  • formAction: 新的 action 函数,用于传递给 form 或 button 组件
  • isPending: 布尔值,表示是否有正在进行的 Transition
tsx 复制代码
'use client'
import { useActionState } from "react"
import { handleLogin } from "../lib/login/actions"
const initialState = { message:'' }
export default function Login() {
    const [state, formAction,isPending] = useActionState(handleLogin, initialState)
    
    return (
        <div>
            <h1>登录页面</h1>
            {isPending && <div>Loading...</div>}
            {state.message}
            <div className="flex flex-col gap-2 w-[300px] mx-auto mt-30">
                <form action={formAction} className="flex flex-col gap-2">
                    <input className="border border-gray-300 rounded-md p-2" type="text" name="username" placeholder="用户名" />
                    <input className="border border-gray-300 rounded-md p-2" type="password" name="password" placeholder="密码" />
                    <button type="submit" className="bg-blue-500 text-white p-2 rounded-md">登录</button>
                </form>
            </div>
        </div>
    )
}
相关推荐
夏幻灵10 分钟前
HTML5里最常用的十大标签
前端·html·html5
Mr Xu_24 分钟前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝28 分钟前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions36 分钟前
2026年,微前端终于“死“了
前端·状态模式
万岳科技系统开发36 分钟前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
程序员猫哥_44 分钟前
HTML 生成网页工具推荐:从手写代码到 AI 自动生成网页的进化路径
前端·人工智能·html
龙飞0544 分钟前
Systemd -systemctl - journalctl 速查表:服务管理 + 日志排障
linux·运维·前端·chrome·systemctl·journalctl
我爱加班、、1 小时前
Websocket能携带token过去后端吗
前端·后端·websocket
AAA阿giao1 小时前
从零拆解一个 React + TypeScript 的 TodoList:模块化、数据流与工程实践
前端·react.js·ui·typescript·前端框架
杨超越luckly1 小时前
HTML应用指南:利用GET请求获取中国500强企业名单,揭秘企业增长、分化与转型的新常态
前端·数据库·html·可视化·中国500强