全书目录

第四十一篇:Qwik 完全指南 —— 可恢复瞬启城区宇宙

第八章:`routeAction$` —— 真正办业务的政务窗口

1 分钟 383 字 第 800 / 962 个阅读单元

如果 routeLoader$ 是“读数据”,那 routeAction$ 就是“写数据”

  • 提交表单
  • 点赞
  • 发评论
  • 修改资料
  • 下单
  • 发邮件
  • 写数据库

官方文档把它说得很清楚:

action 只在显式调用时执行,而且只在服务端执行。

这很关键,因为“写操作”“副作用”本来就不该在渲染阶段乱跑。

来看一个完整例子:

tsx
import { component$ } from '@builder.io/qwik';
import {
  Form,
  routeAction$,
  routeLoader$,
  z,
  zod$,
} from '@builder.io/qwik-city';

export const useArticle = routeLoader$(async ({ params }) => {
  return db.article.findBySlug(params.slug);
});

export const useCreateComment = routeAction$(
  async (data, { params }) => {
    await db.comment.create({
      articleSlug: params.slug,
      content: data.content,
    });

    return {
      success: true,
    };
  },
  zod$({
    content: z.string().min(2, '评论至少 2 个字'),
  })
);

export default component$(() => {
  const article = useArticle();
  const commentAction = useCreateComment();

  return (
    <article>
      <h1>{article.value.title}</h1>

      <Form action={commentAction}>
        <textarea name="content" placeholder="写下你的评论" />
        {commentAction.value?.failed && (
          <p>{commentAction.value.fieldErrors?.content}</p>
        )}
        <button type="submit">发表评论</button>
      </Form>

      {commentAction.value?.success && <p>评论已提交</p>}
    </article>
  );
});

#这里最妙的地方是什么?

<Form /> 的底层是原生 HTML <form>

这意味着:

  • 没 JS 也能提交
  • 有 JS 时,Qwik City 会拦截提交,给你 SPA 体验
  • 但它不是“必须靠客户端框架接管一切”才成立

这非常符合 Qwik 的城区哲学:

优先保留原生 Web 能力,再在其上做可恢复增强。

#routeAction$routeLoader$ 的关系

text
页面进来
  └── routeLoader$ 读数据

用户提交表单
  └── routeAction$ 写数据

写完后页面更新
  └── 相关 loaders 可重新执行

所以你可以粗暴地记:

  • loader = 读
  • action = 写

先别混。