技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
react-router-v7【数据与研究】React Router v7 framework mode conventions for route modules, data loaders, actions, and progressive enhancement. Use when building file-based routes with loaders/actions, handling form submissions with Form/useFetcher/useSubmit, adding route ErrorBoundary components, revalidating data after mutations, or migrating Remix-style route modules to React …
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
React Router v7 framework mode unifies routing, data loading, and mutations into route modules, each exporting a loader, action, default component, and ErrorBoundary for a single URL segment.
app/routes/ (or your configured routes directory) for the URL segment, using nested folders/files for layouts and dynamic segments.useLoaderData() and render UI; keep the component focused on presentation.POST/PUT/DELETE submissions from <Form> or useFetcher; return typed data (including validation errors) instead of throwing for expected failures.<Form>, useFetcher, and useSubmit for mutations so the app works with JavaScript disabled and gets optimistic UI when it's enabled.app/components/ and reusable data access to a services layer (e.g. app/services/posts.server.ts).routes.ts config for advanced/dynamic route trees.<Outlet /> renders the matched child.ErrorBoundary components for recoverable route failures instead of relying on a single app-wide boundary.// app/routes/posts.$postId.tsx
import { Form, useLoaderData, useNavigation } from "react-router";
import type { Route } from "./+types/posts.$postId";
import { getPost, updatePost } from "~/services/posts.server";
export async function loader({ params }: Route.LoaderArgs) {
const post = await getPost(params.postId);
if (!post) {
throw new Response("Not Found", { status: 404 });
}
return { post };
}
export async function action({ request, params }: Route.ActionArgs) {
const formData = await request.formData();
const title = String(formData.get("title") ?? "");
if (title.trim().length === 0) {
return { ok: false as const, errors: { title: "Title is required" } };
}
await updatePost(params.postId, { title });
return { ok: true as const, errors: null };
}
export default function PostDetail() {
const { post } = useLoaderData<typeof loader>();
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
<Form method="post">
<input name="title" defaultValue={post.title} />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Saving..." : "Save"}
</button>
</Form>
);
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
if (error instanceof Response && error.status === 404) {
return <p>Post not found.</p>;
}
return <p>Something went wrong loading this post.</p>;
}
page query param) rather than trusting them downstream.useLoaderData<typeof loader>() rather than duplicating fetch logic in the component.defer, or returning promises for <Await>) only when they measurably improve perceived performance — not by default.POST/PUT/PATCH/DELETE navigations and fetcher submissions to that route.<Form>, useFetcher, and useSubmit for progressive enhancement — these work without client-side JavaScript and layer on optimistic UI when it's available.useEffect.return { errors }) instead of throwing generic exceptions — reserve thrown Responses for actual HTTP-status failures (404, 401, 500)..server.ts modules that are stripped from the client bundle.// Optimistic UI with useFetcher
function LikeButton({ postId, liked }: { postId: string; liked: boolean }) {
const fetcher = useFetcher();
const optimisticLiked = fetcher.formData
? fetcher.formData.get("liked") === "true"
: liked;
return (
<fetcher.Form method="post" action={`/posts/${postId}/like`}>
<input type="hidden" name="liked" value={String(!optimisticLiked)} />
<button type="submit">{optimisticLiked ? "Unlike" : "Like"}</button>
</fetcher.Form>
);
}
useState.useNavigation(), fetcher.state) to show optimistic or loading UI instead of a separate global spinner store.Route.LoaderArgs/Route.ActionArgs types (from ./+types/<route>) for full inference of params and return types.app/services/*.server.ts) rather than mocking fetch deep inside components.useEffect — this causes a loading waterfall and defeats the purpose of route-level data loading.ErrorBoundary instead of route-scoped boundaries that preserve navigation and layout.useEffect) when the underlying loader data is protected — check and redirect inside the loader itself so no protected data is ever fetched or flashed.