Astro 后来变得越来越“像一座完整展馆系统”,一个关键原因就是 Actions。
它解决的问题是:
明明只是一个表单或一次前后端调用
却要自己写 fetch、自己解析 JSON、自己做校验、自己管理错误Astro 的思路是:
把这类“票务窗口型交互”标准化。
先定义 action:
// src/actions/index.ts
import { defineAction, ActionError } from 'astro:actions';
import { z } from 'astro/zod';
export const server = {
reserveVisit: defineAction({
accept: 'form',
input: z.object({
email: z.string().email(),
date: z.coerce.date(),
visitors: z.coerce.number().int().min(1).max(6),
}),
handler: async ({ email, date, visitors }, context) => {
if (!context.locals.user) {
throw new ActionError({
code: 'UNAUTHORIZED',
message: '请先登录后再预约',
});
}
const reservation = await createReservation({
email,
date,
visitors,
userId: context.locals.user.id,
});
return { id: reservation.id };
},
}),
};然后在页面里直接接表单:
---
export const prerender = false;
import { actions } from 'astro:actions';
const result = Astro.getActionResult(actions.reserveVisit);
if (result && !result.error) {
return Astro.redirect(`/reservations/${result.data.id}`);
}
---
<h1>预约参观</h1>
{result?.error && (
<p class="error">预约失败,请检查输入或登录状态。</p>
)}
<form method="POST" action={actions.reserveVisit}>
<label>
邮箱
<input type="email" name="email" required />
</label>
<label>
参观日期
<input type="date" name="date" required />
</label>
<label>
参观人数
<input type="number" name="visitors" min="1" max="6" required />
</label>
<button>提交预约</button>
</form>这段代码有几个非常值钱的点:
- 输入校验和 handler 放在同一个正式窗口
form数据和服务端逻辑天然对接Astro.getActionResult()可以直接读这次票务处理结果- 失败和成功不必自己拼一整套协议
- 这比“我再开一个 API 然后手写 fetch”更顺
但要记住一个硬规则:
用 HTML form action 调 Astro Actions 时
该页面必须是按需渲染
不能还是纯预渲染页面所以通常要:
export const prerender = false;