别再写 API 路由了:Server Actions 才是全栈 React 的终极形态

2025博客之星年度评选已开启 10w+人浏览 1k人参与

前言:一种名为“胶水代码”的疲惫

接上回。咱们用 Next.js App RouterServer Components 实现了服务端直接读数据库。页面加载速度快得离谱,Google 爬虫也爱死我们了。

但是,只要涉及到操作(比如提交表单、点赞、收藏),你的痛苦回忆又回来了。
在这里插入图片描述

按照老规矩(Next.js Pages Router 时代),你要做一个“添加待办事项”的功能,你得这么折腾:

  1. pages/api/todos.ts 里写一个 API Handler,解析 req.body,连数据库写入,返个 JSON。
  2. 在前端组件里,引入 axios
  3. 写个 handleSubmit,调用 axios.post('/api/todos', data)
  4. 处理 loading 状态,处理 error。
  5. 请求成功后,为了让列表更新,还得手动去更新本地 state 或者触发 SWR/React Query 的 mutate

累不累?
我不就为了存一行字吗?为什么要跨越千山万水,写这么多“胶水代码”来连接前后端?

今天,我们要把这层胶水撕掉。欢迎来到 Server Actions 的世界。在这里,前端按钮可以直接调用后端函数


核心魔法:远程过程调用 (RPC) 的文艺复兴

什么是 Server Action?
简单说,就是你在服务器文件里写一个函数,标上 'use server',然后你可以直接把这个函数 import 到前端组件里,绑在 onClick 上。

Next.js 在背后会自动帮你把这个函数调用变成一个 HTTP POST 请求。你看着像是在调用本地函数,其实是在搞 RPC(远程过程调用)

❌ 以前的写法(前后端分离,心也分离):

1. 后端 (pages/api/createTodo.ts):

export default async function handler(req, res) {
  if (req.method === 'POST') {
    const todo = await db.todo.create({ data: req.body });
    res.status(200).json(todo);
  }
}

2. 前端 (components/AddTodo.tsx):

  const [text, setText] = useState('');
  const add = async () => {
    await axios.post('/api/createTodo', { text }); // 还要记 URL
    // 手动刷新列表...
  };
  return Add;
}

✅ Server Actions 写法(合二为一):

我们新建一个 actions.ts 文件,这里面的代码只会在服务器运行

  1. 定义 Action (actions.ts):
import db from '@/lib/db';
import { revalidatePath } from 'next/cache';

export async function createTodo(formData: FormData) {
  const text = formData.get('text');
  
  // 直接连库,无需 API 路由
  await db.todo.create({ data: { text } });

  // !!! 魔法中的魔法:告诉 Next.js "/todos" 路径下的数据过期了
  // 页面会自动重新获取最新数据,界面自动刷新!
  revalidatePath('/todos');
}
  1. 使用 Action (components/AddTodo.tsx):

const AddTodo = () => {
  // 直接把 Server Action 绑在 form 的 action 上
  // 甚至关了 JS 这表单都能提交(渐进增强)
  return (
    
      
      Add
    
  );
}

发现了吗?axios 不见了,useEffect 不见了,手动刷新数据的逻辑也不见了。 你写代码的感觉就像回到了 PHP 时代(褒义),直接跟数据库对话,但享受着 React 的组件化体验。

进阶玩法:如果不只是 Form 提交怎么办?

“将军,我不是提交表单,我就想点个赞,或者点击按钮执行个逻辑,怎么办?”

这时候我们不能用 `` 了,我们需要在 Event Handler 里调用。 但是,直接调用 Server Action 是拿不到 loading 状态的。

这时候,React 的 useTransition 也就是为此而生的。

import { useTransition } from 'react';
import { likePost } from '@/actions'; // 引入服务器函数

const LikeButton = ({ postId }) => {
  let [isPending, startTransition] = useTransition();

  const handleClick = () => {
    // startTransition 会把状态更新标记为“非紧急”
    // 并自动追踪 async 函数的执行状态到 isPending 里
    startTransition(async () => {
      await likePost(postId);
    });
  };

  return (
    
      {isPending ? '点赞中...' : '👍 点赞'}
    
  );
};

这体验简直绝了。 你不需要自己定义 const [loading, setLoading] = useState(false)useTransition 帮你搞定了一切 loading 态管理。

避坑指南:别太天真,这还是 HTTP

虽然写起来像本地函数,但你心里要有数:这依然是一次网络请求

1. 安全,安全,还是TMD安全!

千万别以为这是一个普通函数,就觉得能在里面随便传东西。 Server Action 本质上就是一个公开的 API 接口。 任何人都可以通过抓包,模拟发送 POST 请求给这个 Action。

所以,必须在 Action 内部做身份验证和权限校验!

export async function deletePost(id: string) {
  // !!! 必须检查用户是否有权限!
  const session = await getSession();
  if (!session || session.user.role !== 'admin') {
    throw new Error('你没有权限干这事儿');
  }

  // !!! 必须校验输入参数!(Zod 再次登场)
  const safeId = z.string().parse(id);

  await db.post.delete({ where: { id: safeId } });
}

2. 不能传递复杂对象

因为 Action 调用要跨越网络(序列化),所以参数和返回值必须是可序列化的。 你不能把一个 onClick 回调函数或者一个 Class 实例传给 Server Action。 传 JSON、String、FormData 都是最稳的。

3. 不要把敏感数据传回客户端

Server Action 的返回值会发给浏览器。 别手滑把 user.password_hash 或者整个数据库连接对象给 return 出来了。


总结:全栈的最后一公里

Server Actions 的出现,标志着 React 正式从一个 UI 库,进化成了一个全栈框架。

它打破了“前端”和“后端”之间那道厚厚的墙。

  • 对于简单的 CRUD,你再也不用去写繁琐的 REST API。
  • 你的业务逻辑高度内聚:数据库操作代码和触发它的按钮代码,物理距离只有几行 import。

当然,这并不意味着后端工程师失业了。对于复杂的微服务编排、高并发处理,还是需要专业的后端架构。 但对于我们这种做应用、做产品的开发者来说,Server Actions 让我们能用原来 1/3 的代码量,干完 100% 的活

这就叫生产力。
在这里插入图片描述


下期预告:代码写完了,测试跑通了。最后一步是什么?部署! 你还在手动 SSH 连服务器、装 Nginx、传文件吗?太 Low 了。 下一篇(完结篇),我们来聊聊 “CI/CD 与 Vercel 部署” 。教你如何把 GitHub 代码提交的一瞬间,自动触发构建、部署,并让你的应用跑在全世界的边缘节点(Edge)上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大布布将军

感 蟹 袋 佬🫡🫡🫡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值