全书目录

第三十一篇:Remix 完全指南 —— Web 原生航运港宇宙

五、先看一个最小可工作的泊位

1 分钟 156 字 第 568 / 962 个阅读单元

下面是一个典型的 Remix 路由模块:

tsx
// app/routes/ships.$shipId.tsx
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Form, useLoaderData } from "@remix-run/react";

import { getShipById, updateShipStatus } from "~/models/ship.server";

export async function loader({ params }: LoaderFunctionArgs) {
  const ship = await getShipById(params.shipId!);

  if (!ship) {
    throw new Response("船舶不存在", { status: 404 });
  }

  return json({ ship });
}

export async function action({ request, params }: ActionFunctionArgs) {
  const formData = await request.formData();
  const status = formData.get("status");

  await updateShipStatus(params.shipId!, String(status));

  return redirect(`/ships/${params.shipId}`);
}

export default function ShipDetailRoute() {
  const { ship } = useLoaderData<typeof loader>();

  return (
    <main>
      <h1>{ship.name}</h1>
      <p>当前状态:{ship.status}</p>

      <Form method="post">
        <button type="submit" name="status" value="arrived">
          标记为已到港
        </button>
        <button type="submit" name="status" value="departed">
          标记为已离港
        </button>
      </Form>
    </main>
  );
}

这个文件里,其实已经出现了 Remix 的完整闭环:

  • loader 负责读取当前船的数据
  • 组件负责展示
  • <Form> 负责提交
  • action 负责写入
  • redirect 负责动作完成后的航线跳转

这就是 Remix 的味道:

读写都挂在路由上,UI 只是这个泊位的外立面。