全书目录

第三十五篇:Astro 完全指南 —— 内容展馆群岛宇宙

第十八章:Actions,是票务窗口,不是你手搓 fetch 的重复劳动

1 分钟 262 字 第 670 / 962 个阅读单元

Astro 后来变得越来越“像一座完整展馆系统”,一个关键原因就是 Actions

它解决的问题是:

text
明明只是一个表单或一次前后端调用
却要自己写 fetch、自己解析 JSON、自己做校验、自己管理错误

Astro 的思路是:

把这类“票务窗口型交互”标准化。

先定义 action:

ts
// 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 };
    },
  }),
};

然后在页面里直接接表单:

astro
---
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”更顺

但要记住一个硬规则:

text
用 HTML form action 调 Astro Actions 时
该页面必须是按需渲染
不能还是纯预渲染页面

所以通常要:

ts
export const prerender = false;