在 Astro 页面中构建 HTML 表单
使用按需渲染的 Astro 页面既可以显示又可以处理表单。在本节示例中,你可以使用标准的 HTML 表单将数据提交到服务器。你的 frontmatter 脚本将在服务器端处理数据,而不向客户端发送任何 JavaScript。
:::tip[使用 Astro Actions 构建表单]在 v4.15 中,Astro 引入了 actions,这相比基础的 HTML 表单提供了多项优势,包括验证表单数据和根据表单提交更新界面。若要改用此方法构建表单,请参阅我们的 actions 指南 以了解这些功能的更多信息。:::
前期准备
- 一个安装了服务器适配器的项目
操作步骤
创建一个包含表单和处理代码的
.astro页面。例如,你可以添加一个注册页面(registration page):--- --- <h1>Register</h1>在页面中添加一个
<form>标签,并给它添加一些input标签内容。每个input标签都应该有一个name属性,用于描述该输入字段的值。请确保在表单中包含了
<button>或<input type="submit">元素以提交表单。--- --- <h1>Register</h1> <form> <label> Username: <input type="text" name="username" /> </label> <label> Email: <input type="email" name="email" /> </label> <label> Password: <input type="password" name="password" /> </label> <button>Submit</button> </form>使用校验属性提供基本的客户端校验功能,即使 JavaScript 被禁用这也能正常工作。
在这个例子中,
required属性可以阻止在字段为空时提交表单;minlength属性可以设置输入文本的最小要求长度;type="email"属性还引入了校验功能,只接受符合有效电子邮件格式的输入。
--- --- <h1>Register</h1> <form> <label> Username: <input type="text" name="username" required /> </label> <label> Email: <input type="email" name="email" required /> </label> <label> Password: <input type="password" name="password" required minlength="6" /> </label> <button>Submit</button> </form>:::tip你可以通过使用
<script>标签和约束验证 API 来添加涉及多个字段的自定义验证逻辑。为了更容易编写复杂的验证逻辑,你也可以使用框架组件来构建表单,并选择一个类似 React Hook Form 或 Felte 的表单库。:::
提交表单将导致浏览器重新请求页面。把表单的数据传输属性
method改为POST,表单数据将作为Requestbody 的一部分发送,而不是作为 URL 参数发送。--- --- <h1>Register</h1> <form method="POST"> <label> Username: <input type="text" name="username" required /> </label> <label> Email: <input type="email" name="email" required /> </label> <label> Password: <input type="password" name="password" required minlength="6" /> </label> <button>Submit</button> </form>在 frontmatter 中检查
POST方法,并使用Astro.request.formData()访问表单数据。将其包裹在try ... catch块中,以处理未通过表单发送的POST请求和formData无效的情况。--- export const prerender = false; // 在 'server' 模式下,无需添加该行 if (Astro.request.method === "POST") { try { const data = await Astro.request.formData(); const name = data.get("username"); const email = data.get("email"); const password = data.get("password"); // Do something with the data } catch (error) { if (error instanceof Error) { console.error(error.message); } } } --- <h1>Register</h1> <form method="POST"> <label> Username: <input type="text" name="username" required /> </label> <label> Email: <input type="email" name="email" required /> </label> <label> Password: <input type="password" name="password" required minlength="6" /> </label> <button>Submit</button> </form>在服务器端验证表单数据。这应包括和客户端上的相同的验证方式,以防止你的端点被恶意提交,或者支持少数的不具备表单验证功能的早期旧版浏览器。
此外,还可以进行一些客户端无法完成的验证。例如,该示例检查电子邮件是否已存在于数据库中。
也可以通过将错误信息存储在一个
errors对象中,并在模板中访问该对象,将错误消息发送回客户端。--- export const prerender = false; // 在 'server' 模式下,无需添加该行 import { isRegistered, registerUser } from "../../data/users" import { isValidEmail } from "../../utils/isValidEmail"; const errors = { username: "", email: "", password: "" }; if (Astro.request.method === "POST") { try { const data = await Astro.request.formData(); const name = data.get("username"); const email = data.get("email"); const password = data.get("password"); if (typeof name !== "string" || name.length < 1) { errors.username += "Please enter a username. "; } if (typeof email !== "string" || !isValidEmail(email)) { errors.email += "Email is not valid. "; } else if (await isRegistered(email)) { errors.email += "Email is already registered. "; } if (typeof password !== "string" || password.length < 6) { errors.password += "Password must be at least 6 characters. "; } const hasErrors = Object.values(errors).some(msg => msg) if (!hasErrors) { await registerUser({name, email, password}); return Astro.redirect("/login"); } } catch (error) { if (error instanceof Error) { console.error(error.message); } } } --- <h1>Register</h1> <form method="POST"> <label> Username: <input type="text" name="username" /> </label> {errors.username && <p>{errors.username}</p>} <label> Email: <input type="email" name="email" required /> </label> {errors.email && <p>{errors.email}</p>} <label> Password: <input type="password" name="password" required minlength="6" /> </label> {errors.password && <p>{errors.password}</p>} <button>Register</button> </form>