我正在参加「掘金·启航计划」
React Router v6版本,听说又更新了一版,官网文档也更新了(6.4.2)。这里就汇总翻译一下文档更新的内容。
了解其他 React Router v6 的官网文档,可以看我本专栏往期的文章。
1、关于 Routers
API创建router对象:
createBrowserRouter
类型定义:
function createBrowserRouter(
routes: RouteObject[],
opts?: {
basename?: string;
window?: Window;
}
): RemixRouter;
这个 API 用于使用js代码创建一个 History router。第二个属性是可选参数:
- basename:相对路由根地址
- window:重新指定window对象,常用于测试环境或者非浏览器环境
使用方式如下:
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "team",
element: <Team />,
loader: teamLoader,
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);
createHashRouter
使用方式同 createBrowserRouter,创建一个基于锚点导航的 router。
createMemoryRouter
memory router是一个能够自己管理 history stack 的 History router。常用于开发工具(例如StoryBook)、测试或非浏览器环境。
类型定义:
function createMemoryRouter(
routes: RouteObject[],
opts?: {
basename?: string;
initialEntries?: InitialEntry[];
initialIndex?: number;
window?: Window;
}
): RemixRouter;
使用方式:
import {
RouterProvider,
createMemoryRouter,
} from "react-router-dom";
import * as React from "react";
import {
render,
waitFor,
screen,
} from "@testing-library/react";
import "@testing-library/jest-dom";
import CalendarEvent from "./routes/event";
test("event route", async () => {
const FAKE_EVENT = { name: "test event" };
const routes = [
{
path: "/events/:id",
element: <CalendarEvent />,
loader: () => FAKE_EVENT,
},
];
const router = createMemoryRouter(routes, {
initialEntries: ["/", "/events/123"],
initialIndex: 1,
});
render(<RouterProvider router={router} />);
await waitFor(() => screen.getByRole("heading"));
expect(screen.getByRole("heading")).toHaveTextContent(
FAKE_EVENT.name
);
});
其中的参数:
- initialEntries:初始化的 history stack
- initialIndex:初始化显示的 history stack 的下标
RouterProvider
给上述创建router的API提供一个容器。其有第二个参数 fallbackElement={<SpinnerOfDoom />}
,可以提供一个在路由加载过程中 loading的自定义组件。
2、关于 Route 组件
添加新属性 loader
路由在渲染之前加载的加载器。当路由跳转时,loader函数会异步的执行,并且在目标路由组件中可以获取到。
使用方式:
{
element: <Team />,
path: ":teamId",
loader: async ({ params }) => {
return fetch(`/api/teams/${params.teamId}.json`);
},
},
参数 params 是路由参数。可以返回任何你想要的内容,推荐使用 fetch 或者 Promise。
loader 加载的数据可以在跳转之后的界面 通过 useLoaderData 获取。使用方式见下面 hooks 的更新内容。
添加新属性 action
提供一种路由切换时触发的方法。该方式允许在路由跳转时推送一个 ajax 请求出去。
使用方式:
// 定义
<Route
path="/song/:songId/edit"
element={<EditSong />}
action={async ({ params, request }) => {
let formData = await request.formData();
return fakeUpdateSong(params.songId, formData);
}}
loader={({ params }) => {
return fakeGetSong(params.songId);
}}
/>
// forms
<Form method="post" action="/songs" />;
<fetcher.Form method="put" action="/songs/123/edit" />;
// imperative submissions
let submit = useSubmit();
submit(data, {
method: "delete",
action: "/songs/123",
});
fetcher.submit(data, {
method: "patch",
action: "/songs/123/edit",
});
触发方式为非GET类的请求,并提供两个参数:
- params 路由动态参数
- request fetch请求的实例
errorElement
只适用于API动态创建的router。使用方式:
<Route
path="/invoices/:id"
// if an exception is thrown here
loader={loadInvoice}
// here
action={updateInvoice}
// or here
element={<Invoice />}
// this will render instead of `element`
errorElement={<ErrorBoundary />}
/>;
function Invoice() {
return <div>Happy {path}</div>;
}
function ErrorBoundary() {
let error = useRouteError();
console.error(error);
// Uncaught ReferenceError: path is not defined
return <div>Dang!</div>;
}
提供一种路由跳转错误后的补救方案。在action、loader或者组件渲染过程中抛出错误后,可以在这里捕获并代替显示自定义的错误界面。
shouldRevalidate
作为一个优化项,用于对loader做校验。他是一个函数,在路由 loader 获取数据之前调用,该函数返回一个布尔值,如果返回布尔值,本次 loader 函数将不会调用,界面数据不会改变。
使用方式:
<Route
path="meals-plans"
element={<MealPlans />}
loader={loadMealPlans}
shouldRevalidate={({ currentUrl }) => {
// only revalidate if the submission originates from
// the `/meal-plans/new` route.
return currentUrl.pathname === "/meal-plans/new";
}}
>
<Route
path="new"
element={<NewMealPlanForm />}
// `loadMealPlans` will be revalidated after
// this action...
action={createMealPlan}
/>
<Route
path=":planId/meal"
element={<Meal />}
// ...but not this one because origin the URL
// is not "/meal-plans/new"
action={updateMeal}
/>
</Route>
3、关于路由组件
Await
是一种类似于 React 路由懒加载(lazy)的工具。使用方式如下:
import { Await, useLoaderData } from "react-router-dom";
function Book() {
const { book, reviews } = useLoaderData();
return (
<div>
<h1>{book.title}</h1>
<p>{book.description}</p>
<React.Suspense fallback={<ReviewsSkeleton />}>
<Await
resolve={reviews}
errorElement={
<div>Could not load reviews 😬</div>
}
children={(resolvedReviews) => (
<Reviews items={resolvedReviews} />
)}
/>
</React.Suspense>
</div>
);
}
Form
v6版路由,对于 plain HTML form 进行了一个包装。目前仅仅在使用 API 创建路由实例的场景中使用。
import { Form } from "react-router-dom";
function NewEvent() {
return (
<Form method="post" action="/events">
<input type="text" name="title" />
<input type="text" name="description" />
<button type="submit">Create</button>
</Form>
);
}
属性参数介绍:
- action:form submit 的 url。和原生form唯一不同的是,他的默认url是最近匹配到的相对路由
- method:表单提交的方法
- replace:改变 history stack 的切换方式
- relative:设置相对路由的根路由
- reloadDocument:跳过 React Router 并使用浏览器的内置行为提交表单
综合使用案例:
总结:action 可以接受 element 组件中传入的请求实例,在action中进行使用。而紧接着,element 组件中,又可以通过 useLoaderData 使用 loader 里加载进来的数据。
<Route
path="/projects/:id"
element={<Project />}
loader={async ({ params }) => {
return fakeLoadProject(params.id)
}}
action={async ({ request, params }) => {
switch (request.method) {
case "put": {
let formData = await request.formData();
let name = formData.get("projectName");
return fakeUpdateProject(name);
}
case "delete": {
return fakeDeleteProject(params.id);
}
default {
throw new Response("", { status: 405 })
}
}
}}
/>;
function Project() {
let project = useLoaderData();
return (
<>
<Form method="put">
<input
type="text"
name="projectName"
defaultValue={project.name}
/>
<button type="submit">Update Project</button>
</Form>
<Form method="delete">
<button type="submit">Delete Project</button>
</Form>
</>
);
}
ScrollRestoration
用于在浏览器 location变化后,保持滚动条的位置。仅仅在 API 创建路由对象时有效。
推荐在根路由中使用,使用方式:
import { ScrollRestoration } from "react-router-dom";
function RootRouteComponent() {
return (
<div>
{/* ... */}
<ScrollRestoration
getKey={(location, matches) => {
// default behavior
return location.key;
}}
/>
</div>
);
}
参数:
- getKey:React Router 缓存滚动位置的key
- preventScrollReset:当创建一个新的 scroll key时,页面滚动位置会被重置在顶部。你可以将该参数设置为 true 来阻止页面被重置回顶部
由于篇幅过长,新版文档的hooks和内置方法介绍放到下期进行。下篇在这里:React Router v6 官方文档翻译 (九) ---- 新版新增功能汇总(下)