Fetcher 非常適合用於建立複雜、動態的使用者介面,這些介面需要多次並行資料互動,而不會導致導航。
Fetcher 會追蹤其自身獨立的狀態,可用於載入資料、變更資料、提交表單,以及大致上與載入器和動作互動。
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 將會呼叫動作並自動重新驗證路由資料。
Fetcher 在非同步工作期間使其狀態可用,讓您可以在使用者互動時立即渲染待處理 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>
);
}
Fetcher 的另一個常見用途是從路由載入資料,用於下拉式方塊之類的功能。
請考慮下列具有非常基本搜尋功能的路由
// { 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 可以透過程式設計方式使用 fetcher.submit
提交
<fetcher.Form method="get" action="/search-users">
<input
type="text"
name="q"
onChange={(event) => {
fetcher.submit(event.currentTarget.form);
}}
/>
</fetcher.Form>
請注意,輸入事件的表單會以第一個引數傳遞至 fetcher.submit
。Fetcher 將使用該表單提交請求,讀取其屬性並序列化其元素中的資料。