2025 年讓 React 邏輯更干凈的秘訣?集中式 Action Routes 魔法
最近在做一個 React 項目,發(fā)現(xiàn)到處都是重復(fù)代碼。React Router 的 Action Routes 救了我:把“資源相關(guān)的操作”在客戶端與服務(wù)端之間都集中起來統(tǒng)一處理。
現(xiàn)在,應(yīng)用更順滑、擴展也更輕松。下面一起看看,如何在 React + React Router 中使用 Action Route。
什么是 Action Routes?
React Router 的 Action Routes 讓我們在一個集中位置處理特定的用戶操作:例如創(chuàng)建、更新、刪除應(yīng)用中的資源。
它支持把服務(wù)端邏輯與客戶端邏輯放在與某條路由綁定的同一個文件里,然后在全站任意地方通過表單、按鈕或 fetcher 去觸發(fā)這些動作。
用 Action Routes 搭建應(yīng)用
把所有“動作邏輯”集中到一處后,表單處理、API 調(diào)用與錯誤處理會變得更簡單: 調(diào)試更少、一致性更強、寫代碼更開心。
Step 1:在路由里配置你的 Actions
我會新建一個 routes/actions 文件夾來放“動作”邏輯,例如 user-create.ts。然后把它們掛進路由系統(tǒng),實現(xiàn)無縫路由。
// routes.ts(路由配置入口)
import type { RouteConfig } from "@react-router/dev/routes";
import { prefix } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
const [routes, actionRoutes] = await Promise.all([
flatRoutes({ rootDirectory: "./routes" }),
flatRoutes({ rootDirectory: "./routes/actions" }),
]);
export default [
...routes,
...prefix("/actions", actionRoutes),
] satisfies RouteConfig;像 /actions/user-create 這樣的端點就會干凈很多。
Step 2:編寫可復(fù)用的 Action 邏輯
用“資源 + 任務(wù)”給動作命名會很清晰。每個動作處理輸入與服務(wù)端邏輯,并返回成功或失敗的響應(yīng)。
// /actions/post-create.ts(創(chuàng)建帖子)
export async function action({ request }: Route.ActionArgs) {
let user = await authenticate(request);
if (!user) {
return unauthorized({ message: "You must be logged in to create a post." });
}
let formData = await request.formData();
let result = z
.object({ title: z.string(), content: z.string() })
.safeParse(Object.fromEntries(formData));
if (!result.success) {
return badRequest({
message: "Invalid input data",
errors: z.treeifyError(result.error),
});
}
let post = await Post.create({
userId: user.id,
title: result.data.title,
content: result.data.content,
});
return created({ message: "Post created successfully", post });
}Step 3:管理客戶端副作用
動作完成后,需要給到界面反饋:成功就重定向或彈出確認;失敗就展示清晰的錯誤信息。下面是一個在客戶端處理不同場景的小工具函數(shù):
// 統(tǒng)一在客戶端“消費”服務(wù)端的 action 結(jié)果:成功提示 / 跳轉(zhuǎn);失敗提示
export async function clientAction({
serverAction,
params,
}: Route.ClientActionArgs) {
let result = await serverAction();
if (result.status < 300) {
toast.success(result.message);
return redirect(href("/posts/:postId", { postId: result.post.id }));
} else if (result.status >= 400) {
toast.error(result.message);
}
return result;
}這種模式可以讓你:
- 統(tǒng)一調(diào)用服務(wù)端 action;
- 成功時執(zhí)行副作用(如 toast / 跳轉(zhuǎn));
- 失敗時執(zhí)行副作用(如錯誤 toast);
- 同時把結(jié)果返回給客戶端繼續(xù)使用。
Step 4:加入本地化消息
結(jié)合 i18n,讓錯誤信息在多語言環(huán)境下都更友好。
// 使用 i18n 返回本地化的錯誤提示
import { i18n } from "~/middleware/i18n";
export async function action({ request, context }: Route.ActionArgs) {
let { t } = i18n(context);
return badRequest({ message: t("actions.createPost.errors.generic") });
}Step 5:用鑒權(quán)保障安全
在動作執(zhí)行前校驗訪問權(quán)限;未授權(quán)用戶返回 403。
// 在 action 前置校驗權(quán)限,不通過則返回 403
if (!(await hasActiveSubscription(user))) {
return forbidden({ message: "You don't have permission to create a post." });
}總結(jié)
Action Routes 讓代碼既精簡又可擴展: 它幫你消除重復(fù)、簡化調(diào)試,將零散的邏輯拉回到單一真相來源。
為什么要和混亂的代碼搏斗?用這套模式,構(gòu)建經(jīng)得起時間考驗的應(yīng)用。


























