Fetchers 对于创建复杂、动态的用户界面非常有用,这些界面需要多个并发的数据交互,而不会导致导航。
Fetchers 跟踪其自身独立的状态,可用于加载数据、更改数据、提交表单以及通常与加载器和操作进行交互。
Fetcher 最常见的情况是将数据提交给操作,从而触发路由数据的重新验证。考虑以下路由模块
import { useLoaderData } from "react-router";
export async function clientLoader({ request }) {
let title = localStorage.getItem("title") || "No Title";
return { title };
}
export default function Component() {
let data = useLoaderData();
return (
<div>
<h1>{data.title}</h1>
</div>
);
}
首先,我们将为 fetcher 调用的路由添加一个操作
import { useLoaderData } from "react-router";
export async function clientLoader({ request }) {
// ...
}
export async function clientAction({ request }) {
await new Promise((res) => setTimeout(res, 1000));
let data = await request.formData();
localStorage.setItem("title", data.get("title"));
return { ok: true };
}
export default function Component() {
let data = useLoaderData();
// ...
}
接下来,创建一个 fetcher 并使用它渲染一个表单
import { useLoaderData, useFetcher } from "react-router";
// ...
export default function Component() {
let data = useLoaderData();
let fetcher = useFetcher();
return (
<div>
<h1>{data.title}</h1>
<fetcher.Form method="post">
<input type="text" name="title" />
</fetcher.Form>
</div>
);
}
如果现在提交表单,fetcher 将调用操作并自动重新验证路由数据。
在异步工作期间,Fetchers 会提供其状态,以便您可以在用户交互的瞬间渲染加载 UI。
export default function Component() {
let data = useLoaderData();
let fetcher = useFetcher();
return (
<div>
<h1>{data.title}</h1>
<fetcher.Form method="post">
<input type="text" name="title" />
{fetcher.state !== "idle" && <p>Saving...</p>}
</fetcher.Form>
</div>
);
}
有时表单中包含足够的信息来立即渲染下一个状态。您可以使用 fetcher.formData
访问表单数据。
export default function Component() {
let data = useLoaderData();
let fetcher = useFetcher();
let title = fetcher.formData?.get("title") || data.title;
return (
<div>
<h1>{title}</h1>
<fetcher.Form method="post">
<input type="text" name="title" />
{fetcher.state !== "idle" && <p>Saving...</p>}
</fetcher.Form>
</div>
);
}
从操作返回的数据可在 fetcher 的 data
属性中获取。这主要用于将错误消息返回给用户,以指示突变失败。
// ...
export async function clientAction({ request }) {
await new Promise((res) => setTimeout(res, 1000));
let data = await request.formData();
let title = data.get("title") as string;
if (title.trim() === "") {
return { ok: false, error: "Title cannot be empty" };
}
localStorage.setItem("title", title);
return { ok: true, error: null };
}
export default function Component() {
let data = useLoaderData();
let fetcher = useFetcher();
let title = fetcher.formData?.get("title") || data.title;
return (
<div>
<h1>{title}</h1>
<fetcher.Form method="post">
<input type="text" name="title" />
{fetcher.state !== "idle" && <p>Saving...</p>}
{fetcher.data?.error && (
<p style={{ color: "red" }}>
{fetcher.data.error}
</p>
)}
</fetcher.Form>
</div>
);
}
Fetchers 的另一个常见用例是从路由加载数据,例如用于组合框。
考虑以下具有非常基本搜索功能的路由
// { path: '/search-users', filename: './search-users.tsx' }
const users = [
{ id: 1, name: "Ryan" },
{ id: 2, name: "Michael" },
// ...
];
export async function loader({ request }) {
await new Promise((res) => setTimeout(res, 300));
let url = new URL(request.url);
let query = url.searchParams.get("q");
return users.filter((user) =>
user.name.toLowerCase().includes(query.toLowerCase())
);
}
import { useFetcher } from "react-router";
export function UserSearchCombobox() {
let fetcher = useFetcher();
return (
<div>
<fetcher.Form method="get" action="/search-users">
<input type="text" name="q" />
</fetcher.Form>
</div>
);
}
import { useFetcher } from "react-router";
import type { Search } from "./search-users";
export function UserSearchCombobox() {
let fetcher = useFetcher<typeof Search.action>();
// ...
}
确保使用 import type
,以便仅导入类型。
import { useFetcher } from "react-router";
export function UserSearchCombobox() {
let fetcher = useFetcher<typeof Search.action>();
return (
<div>
<fetcher.Form method="get" action="/search-users">
<input type="text" name="q" />
</fetcher.Form>
{fetcher.data && (
<ul>
{fetcher.data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
);
}
请注意,您需要按“Enter”键提交表单才能查看结果。
import { useFetcher } from "react-router";
export function UserSearchCombobox() {
let fetcher = useFetcher<typeof Search.action>();
return (
<div>
<fetcher.Form method="get" action="/search-users">
<input type="text" name="q" />
</fetcher.Form>
{fetcher.data && (
<ul
style={{
opacity: fetcher.state === "idle" ? 1 : 0.25,
}}
>
{fetcher.data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
);
}
可以使用 fetcher.submit
以编程方式提交 Fetchers。
<fetcher.Form method="get" action="/search-users">
<input
type="text"
name="q"
onChange={(event) => {
fetcher.submit(event.currentTarget.form);
}}
/>
</fetcher.Form>
请注意,输入事件的表单作为第一个参数传递给 fetcher.submit
。Fetcher 将使用该表单提交请求,读取其属性并序列化其元素中的数据。