表单与变更
用 routeAction$ 与 <Form> 处理写操作,没有 JavaScript 也能提交。
routeAction$() 定义只在服务端执行、且只在被显式调用时运行的操作。它适合写数据库、发邮件、创建订单这类有副作用的逻辑。
import { component$ } from '@qwik.dev/core';
import { routeAction$, Form } from '@qwik.dev/router';
export const useSignup = routeAction$(async (data) => {
const user = await db.users.create({
name: data.name,
email: data.email,
});
return { success: true, id: user.id };
});
export default component$(() => {
const signup = useSignup();
return (
<Form action={signup}>
<input name="name" required />
<input name="email" type="email" required />
<button type="submit">创建账号</button>
{signup.value?.success && <p>欢迎,{signup.value.id}</p>}
</Form>
);
});01<Form> 的双重身份#
- 没有 JavaScript 时,它就是一个普通 HTML 表单,浏览器直接 POST 到 action 端点。
- 有 JavaScript 时,它拦截提交,在 SPA 中调用 action,不刷新页面。
- action 完成后,可自动刷新被 invalidate 的 loader,让页面数据保持一致。
提交过程中 action.isRunning 为 true,适合禁用按钮或显示进度。action.formData 保留上一次提交的字段,刷新后也能回填。
<button type="submit" disabled={signup.isRunning}>
{signup.isRunning ? '提交中…' : '创建账号'}
</button>02校验与失败#
Qwik Router 内置与 Zod 的集成:zod$() 在 action 执行前校验表单,类型会自动收窄;失败信息进入 fieldErrors。zod$ 从 @qwik.dev/router 导入,回调参数就是 zod 实例(项目里需要先装 zod)。
import { routeAction$, zod$ } from '@qwik.dev/router';
export const useSignup = routeAction$(
async (data) => {
const user = await db.users.create(data);
return { success: true, id: user.id };
},
zod$((z) => ({
name: z.string().min(2),
email: z.string().email(),
}))
);<input name="email" />
{signup.value?.failed && (
<p role="alert">{signup.value.fieldErrors.email}</p>
)}业务层失败使用 fail(status, data) 返回,而不是抛异常。这样页面仍能渲染,用户看到的是可恢复的错误提示。
if (!user) {
return fail(409, { message: '邮箱已被注册' });
}03代码中调用#
不是所有提交都来自表单。action.submit(data) 返回 Promise,适合按钮触发的操作或先做客户端校验。
const action = useDeleteTodo();
<button
onClick$={async () => {
const { value } = await action.submit({ id: todo.id });
if (value.failed) return;
todos.value = todos.value.filter((t) => t.id !== todo.id);
}}
>
删除
</button>04routeAction$ 与 globalAction$#
| routeAction$ | globalAction$ | |
|---|---|---|
| 声明位置 | routes 下的 index/layout | src 任意位置 |
| 作用域 | 当前路由 | 全站 |
| 典型场景 | 受保护页面、路由相关写操作 | 登录、全局搜索、共享操作 |
表单提交后想跳转,在 action 中 throw redirect(303, '/done')。当 JavaScript 关闭时,这会形成 Post/Redirect/Get 流程,刷新结果页不会重复提交。