我正在参加「掘金·启航计划」
React Router v6版本,听说又更新了一版,官网文档也更新了(6.4.2)。这里就汇总翻译一下文档更新的内容。
本文为接上一篇接着讲的。了解其他 React Router v6 的官网文档,可以看我本专栏往期的文章。
Hooks
useActionData
提供上一个跳转时 action 的返回值。如果没有返回值,则是 undefined。
使用方式(最常用的就是表单验证):
import {
useActionData,
Form,
redirect,
} from "react-router-dom";
export default function SignUp() {
// 2. 获取 action 返回值
const errors = useActionData();
return (
<Form method="post">
<p>
<input type="text" name="email" />
{errors?.email && <span>{errors.email}</span>}
</p>
<p>
<input type="text" name="password" />
{errors?.password && <span>{errors.password}</span>}
</p>
<p>
<button type="submit">Sign up</button>
</p>
</Form>
);
}
export async function action({ request }) {
const formData = await request.formData();
const email = formData.get("email");
const password = formData.get("password");
const errors = {};
// validate the fields
if (typeof email !== "string" || !email.includes("@")) {
errors.email =
"That doesn't look like an email address";
}
if (typeof password !== "string" || password.length < 6) {
errors.password = "Password must be > 6 characters";
}
// return data if we have errors
if (Object.keys(errors).length) {
// 1. action 中 return 一个对象,而不是 redirect
return errors;
}
// otherwise create the user and redirect
await createUser(email, password);
return redirect("/dashboard");
}
useAsyncError
返回 Await 组件 的reject的值:
import { useAsyncError, Await } from "react-router-dom";
function ErrorElement() {
const error = useAsyncError();
return (
<p>Uh Oh, something went wrong! {error.message}</p>
);
}
<Await
resolve={promiseThatRejects}
errorElement={<ErrorElement />}
/>;
useAsyncValue
返回 Await 组件的 resolve 值:
function ProductVariants() {
const variants = useAsyncValue();
return <div>{/* ... */}</div>;
}
// Await creates the context for the value
<Await resolve={somePromiseForProductVariants}>
<ProductVariants />
</Await>;
useFetcher
useFetcher仅仅在使用 API 创建router实例中有效。
当你需要:
- 获取与 UI 路由无关的数据(弹出框、动态表单等)
- 无需导航即可将数据提交的操作(共享组件,如实时通讯注册)
- 处理列表中的多个并发提交(典型的“待办事项应用程序”列表,您可以在其中单击多个按钮,并且所有按钮都应同时处于待处理状态)
- 无限滚动容器
等等,useFetcher 可以帮助到你:
import { useFetcher } from "react-router-dom";
function SomeComponent() {
const fetcher = useFetcher();
// 提交动作
React.useEffect(() => {
fetcher.submit(data, options);
fetcher.load(href);
}, [fetcher]);
// 可以直接获取数据
fetcher.state;
fetcher.formData;
fetcher.formMethod;
fetcher.formAction;
fetcher.data;
// 不会触发路由导航的表单
return <fetcher.Form />;
}
fetchers 有很多内置的行为:
- 中断时,自动处理fetch取消
- 使用 POST、PUT、PATCH、DELETE 提交时,首先调用该操作:
- 操作完成后,页面上的数据将重新验证以捕获可能发生的任何突变,自动使您的 UI 与您的服务器状态保持同步
- 当多个 fetcher 同时运行时,它会:
- 在他们每次登陆时提交最新的可用数据
- 确保没有陈旧的负载覆盖更新的数据,无论响应返回的顺序如何
- 通过呈现最近的 errorElement 来处理未捕获的错误(就像来自 <Link> 或 <Form> 的正常导航)
- 如果您的操作/加载程序被调用返回重定向,将重定向应用程序(就像来自 <Link> 或 <Form> 的正常导航一样)
其中各个属性的讲解:
fetcher.state
: 提交的状态,枚举如下:- idle - nothing is being fetched.
- submitting - A route action is being called due to a fetcher submission using POST, PUT, PATCH, or DELETE
- loading - The fetcher is calling a loader (from a
fetcher.load
) or is being revalidated after a separate submission oruseRevalidator
call
fetcher.Form
: 不会导航的表单fetcher.load
: 通过 router loader 加载数据:
if (fetcher.state === "idle" && !fetcher.data) {
fetcher.load("/some/route");
}
fetcher.submit
: 模拟fetcher.Form 的提交动作。比如一个用户登出的操作:
import { useFetcher } from "react-router-dom";
import { useFakeUserIsIdle } from "./fake/hooks";
export function useIdleLogout() {
const fetcher = useFetcher();
const userIsIdle = useFakeUserIsIdle();
useEffect(() => {
if (userIsIdle) {
fetcher.submit(
{ idle: true },
{ method: "post", action: "/logout" }
);
}
}, [userIsIdle]);
}
fetcher.data
: loader 或者 action 返回的数据会存储在这里fetcher.formData
: 当使用<fetcher.Form>
orfetcher.submit()
时,formData 可以用来获取提交数据:
function TaskCheckbox({ task }) {
let fetcher = useFetcher();
// 通过 get 方法可以获取到formData内的值,反向影响提交表单的渲染
let status =
fetcher.formData?.get("status") || task.status;
let isComplete = status === "complete";
return (
<fetcher.Form method="post">
<button
type="submit"
name="status"
value={isComplete ? "incomplete" : "complete"}
>
{isComplete ? "Mark Incomplete" : "Mark Complete"}
</button>
</fetcher.Form>
);
}
fetcher.formAction
: 获取当前提交的 action 的 urlfetcher.formMethod
: 获取当前提交的 action 的 method
useFetchers
返回所有fetcher的数组,但不携带 load
, submit
, Form
信息。
useFormAction
类型定义:
declare function useFormAction(
action?: string,
{ relative }: { relative?: RelativeRoutingType } = {}
): string;
对action 的url包装了一层,会自动解析相对路径。该使用不太常见。
使用方式:
import { useFormAction } from "react-router-dom";
function DeleteButton() {
return (
<button
formAction={useFormAction("destroy")}
formMethod="post"
>
Delete
</button>
);
}
useLoaderData
用户获取route loader的返回值。
使用方式:
import {
createBrowserRouter,
RouterProvider,
useLoaderData,
} from "react-router-dom";
function loader() {
return fetchFakeAlbums();
}
export function Albums() {
const albums = useLoaderData();
// ...
}
const router = createBrowserRouter([
{
path: "/",
loader: loader,
element: <Albums />,
},
]);
ReactDOM.createRoot(el).render(
<RouterProvider router={router} />
);
useLoaderData 不会重复提取数据。它只是读取 React Router 内部管理的 fetch 的结果,当它由于路由之外的原因重新渲染时,您无需担心它会重新获取。这也意味着返回数据和渲染之间,data是稳定的,因此您可以安全地将其传递给 React 钩子(如 useEffect)中的依赖数组。只有在操作或某些导航后再次调用加载程序时,它才会更改。
useMatches
返回当前匹配的路由项。使用案例:面包屑导航:
// 路由表
<Route element={<Root />}>
<Route
path="messages"
element={<Messages />}
loader={loadMessages}
handle={{
// you can put whatever you want on a route handle
// here we use "crumb" and return some elements,
// this is what we'll render in the breadcrumbs
// for this route
crumb: () => <Link to="/message">Messages</Link>,
}}
>
<Route
path="conversation/:id"
element={<Thread />}
loader={loadThread}
handle={{
// `crumb` is your own abstraction, we decided
// to make this one a function so we can pass
// the data from the loader to it so that our
// breadcrumb is made up of dynamic content
crumb: (data) => <span>{data.threadName}</span>,
}}
/>
</Route>
</Route>
// 导航组件
function Breadcrumbs() {
let matches = useMatches();
let crumbs = matches
// first get rid of any matches that don't have handle and crumb
.filter((match) => Boolean(match.handle?.crumb))
// now map them into an array of elements, passing the loader
// data to each one
.map((match) => match.handle.crumb(match.data));
return (
<ol>
{crumbs.map((crumb, index) => (
<li key={index}>{crumb}</li>
))}
</ol>
);
}
useNavigation
获取路由加载过程中的各种导航参数,使用场景:
- 全局加载 loading
- 需要全局禁用 form
- 提交时表单处理中的动画
- ...
使用方式:
import { useNavigation } from "react-router-dom";
function SomeComponent() {
const navigation = useNavigation();
navigation.state; // idle → submitting → loading → idle
navigation.location;
navigation.formData;
navigation.formAction;
navigation.formMethod;
}
useRevalidator
用于重新验证数据的场景,使用方式:
import { useRevalidator } from "react-router-dom";
function WindowFocusRevalidator() {
let revalidator = useRevalidator();
useFakeWindowFocus(() => {
revalidator.revalidate();
});
return (
<div hidden={revalidator.state === "idle"}>
Revalidating...
</div>
);
}
useRouteError
在 errorElement属性组件内部使用,返回任何路由中出现的错误。使用方式:
function ErrorBoundary() {
const error = useRouteError();
console.error(error);
return <div>{error.message}</div>;
}
<Route
errorElement={<ErrorBoundary />}
loader={() => {
// unexpected errors in loaders/actions
something.that.breaks();
}}
action={() => {
// stuff you throw on purpose in loaders/actions
throw new Response("Bad Request", { status: 400 });
}}
element={
// and errors thrown while rendering
<div>{breaks.while.rendering}</div>
}
/>;
useRouteLoaderData
收集路由树上所有的loader数据。在需要全局把控多嵌套路由参数时比较有用。
使用方式:
createBrowserRouter([
{
path: "/",
loader: () => fetchUser(),
element: <Root />,
id: "root",
children: [
{
path: "jobs/:jobId",
loader: loadJob,
element: <JobListing />,
},
],
},
]);
...
const user = useRouteLoaderData("root");
通过路由的id来识别路由树的起点。
useSubmit
动态提交表单。使用方式:
import { useSubmit, Form } from "react-router-dom";
function SearchField() {
let submit = useSubmit();
return (
<Form
onChange={(event) => {
submit(event.currentTarget, { method: "post", action: "/check" });
}}
>
<input type="text" name="search" />
<button type="submit">Search</button>
</Form>
);
}
新增小工具
json
json转换工具:
import { json } from "react-router-dom";
const loader = async () => {
const data = getSomeData();
return json(data);
};
redirect
重定向。在 loader 和 action 中推荐使用,而不是 useNavigate,他等价于如下操作:
new Response("", {
status: 302,
headers: {
Location: someUrl,
},
});
使用案例:
import { redirect } from "react-router-dom";
const loader = async () => {
const user = await getUser();
if (!user) {
return redirect("/login");
}
};