適(shi)合(he)初學者(zhe)的綜(zong)合(he) 備忘清單
$ npm create vite@latest myApp --\
--template react
# 按照提示操(cao)作
$ cd <myApp>
$ npm install react-router-dom \
localforage \
match-sorter \
sort-by
$ npm run dev
import React from "react";
import ReactDOM from "react-dom/client";
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <div>Hello world!</div>,
},
]);
const root=document.getElementById('root');
ReactDOM.createRoot(root).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
import Root from "./routes/root";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
},
]);
import ErrorPage from "./error-page";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
},
]);
contacts
用戶界面import Contact from "./routes/contact";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
},
{
path: "contacts/:contactId",
element: <Contact />,
},
]);
src/main.jsx
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
},
],
},
]);
src/routes/root.jsx
import { Outlet } from "react-router-dom";
export default function Root() {
return (
<>
{/* 所有其(qi)他元素 */}
<div id="detail">
<Outlet />
</div>
</>
);
}
import {
Outlet, Link
} from "react-router-dom";
export default function Root() {
return (
<ul>
<li>
<Link to={`contacts/1`}>
Your Name
</Link>
</li>
<li>
<Link to={`contacts/2`}>
Your Friend
</Link>
</li>
</ul>
);
}
創建動作并將 <form>
更改為 <Form>
import {
Outlet,
Link,
useLoaderData,
Form,
} from "react-router-dom";
import {
getContacts, createContact
} from "../contacts";
export async function action() {
const contact = await createContact();
return { contact };
}
export default function Root() {
const { contacts } = useLoaderData();
return (
<div id="sidebar">
<h1>React Router Contacts</h1>
<Form method="post">
<button type="submit">New</button>
</Form>
</div>
);
}
導入并設置路由上的 action
import Root, {
loader as rootLoader,
action as rootAction,
} from "./routes/root";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
},
],
},
]);
[
{
path: "contacts/:contactId",
element: <Contact />,
},
];
:contactId
URL 段。 冒號(動態段
”
import {
useLoaderData
} from "react-router-dom";
import { getContact } from "../contacts";
export async function loader({ params }) {
return getContact(params.contactId);
}
export default function Contact() {
const contact = useLoaderData();
// existing code
}
在路由上配置 loader
import Contact, {
loader as contactLoader,
} from "./routes/contact";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
loader: contactLoader,
},
],
},
]);
import {
Form, useLoaderData
} from "react-router-dom";
export default function EditContact() {
const contact = useLoaderData();
return (
<Form method="post" id="contact-form">
<label>
<span>Twitter</span>
<input
type="text"
name="twitter"
placeholder="@jack"
defaultValue={contact.twitter}
/>
</label>
<label>
<span>Notes</span>
<textarea
name="notes"
defaultValue={contact.notes}
rows={6}
/>
</label>
<p>
<button type="submit">保(bao)存</button>
<button type="button">取消</button>
</p>
</Form>
);
}
添加新的編輯路由
import EditContact from "./routes/edit";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
loader: contactLoader,
},
{
path: "contacts/:contactId/edit",
element: <EditContact />,
loader: contactLoader,
},
],
},
]);
import {
NavLink,
} from "react-router-dom";
<NavLink
to={`contacts/${contact.id}`}
className={({ isActive, isPending }) =>
isActive
? "active"
: isPending
? "pending"
: ""
}
>
{/* other code */}
</NavLink>
import {
useNavigation,
} from "react-router-dom";
export default function Root() {
const { contacts } = useLoaderData();
const navigation = useNavigation();
return (
<div
id="detail"
className={
navigation.state === 'loading'
? 'loading' : ''
}
>
<Outlet />
</div>
);
}
向(xiang)編輯(ji)模塊(kuai)添加一(yi)個動作
import {
Form,
useLoaderData,
redirect,
} from "react-router-dom";
import { updateContact } from "../contacts";
export async function action({ request, params }) {
const formData = await request.formData();
const updates = Object.fromEntries(formData);
await updateContact(params.contactId, updates);
return redirect(`/contacts/${params.contactId}`);
}
將動作連接到路由
import EditContact, {
action as editAction,
} from "./routes/edit";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
loader: contactLoader,
},
{
path: "contacts/:contactId/edit",
element: <EditContact />,
loader: contactLoader,
action: editAction,
},
],
},
]);
<Form
method="post"
action="destroy"
onSubmit={(event) => {
if (!confirm("請確認您(nin)要刪除此記錄")) {
event.preventDefault();
}
}}
>
<button type="submit">刪除(chu)</button>
</Form>
添加銷毀動作
import {redirect} from "react-router-dom";
import {deleteContact} from "../contacts";
export async function action({ params }) {
await deleteContact(params.contactId);
return redirect("/");
}
將 destroy
路由添(tian)加到路由配置(zhi)中
import {
action as destroyAction
} from "./routes/destroy";
const router = createBrowserRouter([
{
path: "/",
children: [
{
path: "contacts/:contactId/destroy",
action: destroyAction,
},
],
},
]);
export async function action({ params }) {
throw new Error("oh dang!");
await deleteContact(params.contactId);
return redirect("/");
}
讓我們為 destroy
路由(you)創建上下文錯(cuo)誤消(xiao)息:
[
{
path: "contacts/:contactId/destroy",
action: destroyAction,
errorElement: <div>哎(ai)呀!有一個(ge)錯(cuo)誤</div>,
},
];
import Index from "./routes/index";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{ index: true, element: <Index /> },
],
},
]);
import {
Form,
useLoaderData,
redirect,
useNavigate,
} from "react-router-dom";
export default function Edit() {
const contact = useLoaderData();
const navigate = useNavigate();
return (
<Form method="post" id="contact-form">
<div>
<button type="submit">保存</button>
<button
type="button"
onClick={() => {
navigate(-1);
}}
>
取消
</button>
</div>
</Form>
);
}
將 <form>
更改為 <Form>
<Form id="search-form" role="search">
<input
id="q"
aria-label="Search contacts"
placeholder="Search"
type="search"
name="q"
/>
</Form>
如果有 URLSearchParams
過濾列表
export async function loader({ request }) {
const url = new URL(request.url);
const q = url.searchParams.get("q");
const contacts = await getContacts(q);
return { contacts };
}
export async function loader({ request }) {
const url = new URL(request.url);
const q = url.searchParams.get("q");
const contacts = await getContacts(q);
return { contacts, q };
}
export default function Root() {
const { contacts, q } = useLoaderData();
const navigation = useNavigation();
return (
<Form id="search-form" role="search">
<input
id="q"
aria-label="Search contacts"
placeholder="Search"
type="search"
name="q"
defaultValue={q}
/>
{/* existing code */}
</Form>
);
}
import { useEffect } from "react";
export default function Root() {
const { contacts, q } = useLoaderData();
const navigation = useNavigation();
useEffect(() => {
document.getElementById("q").value = q;
}, [q]);
}
import {
useSubmit,
} from "react-router-dom";
export default function Root() {
const { contacts, q } = useLoaderData();
const navigation = useNavigation();
const submit = useSubmit();
return (
<Form id="search-form" role="search">
<input
id="q"
aria-label="Search contacts"
placeholder="Search"
type="search"
name="q"
defaultValue={q}
onChange={(event) => {
submit(event.currentTarget.form);
}}
/>
{/* existing code */}
</Form>
);
}
在 v6.4 中,引入了(le)支持(chi)新(xin)(xin)數(shu)據 API 的(de)新(xin)(xin)路(lu)由器:
:-- | -- |
---|---|
createBrowserRouter | |
createMemoryRouter | |
createHashRouter |
以下路由器不支持數據 data
API:
:-- | -- |
---|---|
<BrowserRouter> | |
<MemoryRouter> | |
<HashRouter> | |
<NativeRouter> | |
<StaticRouter> |
快速更新到 v6.4 的最簡單方法是從 createRoutesFromElements 獲得幫助,因此您無需將 <Route>
元(yuan)素轉(zhuan)換(huan)為路(lu)由對象
import {
createBrowserRouter,
createRoutesFromElements,
Route,
RouterProvider,
} from "react-router-dom";
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<Root />}>
<Route path="dashboard" element={<Dashboard />} />
{/* ... etc. */}
</Route>
)
);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
import Root, {rootLoader} from "./root";
import Team, {teamLoader} from "./team";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "team",
element: <Team />,
loader: teamLoader,
},
],
},
]);
const root=document.getElementById('root');
ReactDOM.createRoot(root).render(
<RouterProvider router={router} />
);
Type Declaration
function createBrowserRouter(
routes: RouteObject[],
opts?: {
basename?: string;
window?: Window;
}
): RemixRouter;
routes
createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "events/:id",
element: <Event />,
loader: eventLoader,
},
],
},
]);
basename
用于(yu)您無(wu)法(fa)部(bu)署到域的(de)根(gen)目(mu)錄,而是部(bu)署到子目(mu)錄的(de)情況
createBrowserRouter(routes, {
basename: "/app",
});
createBrowserRouter(routes, {
basename: "/app",
});
<Link to="/" />;
// results in <a href="/app" />
createBrowserRouter(routes, {
basename: "/app/",
});
<Link to="/" />;
// results in <a href="/app/" />
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
createHashRouter,
RouterProvider,
} from "react-router-dom";
import Root, { rootLoader } from "./root";
import Team, { teamLoader } from "./team";
const router = createHashRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "team",
element: <Team />,
loader: teamLoader,
},
],
},
]);
const root=document.getElementById('root');
ReactDOM.createRoot(root).render(
<RouterProvider router={router} />
);
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 "./event";
test("event route", async () => {
const FAKE_EVENT = { name: "測試事件" };
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
createMemoryRouter(routes, {
initialEntries: ["/", "/events/123"],
});
initialIndex
createMemoryRouter(routes, {
initialEntries: ["/", "/events/123"],
initialIndex: 1,
// start at "/events/123"
});
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />,
},
],
},
]);
const root=document.getElementById('root');
ReactDOM.createRoot(root).render(
<RouterProvider
router={router}
fallbackElement={<BigSpinner />}
/>
);
fallbackElement
如(ru)果(guo)您不是服務器渲染您的應(ying)(ying)用程序,DataBrowserRouter 將在(zai)安裝(zhuang)時(shi)啟動所有匹配(pei)的路(lu)由加載器。 在(zai)此期間,您可以提供一個 fallbackElement 來向用戶表明(ming)該應(ying)(ying)用程序正在(zai)運(yun)行
<RouterProvider
router={router}
fallbackElement={<SpinnerOfDoom />}
/>
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
BrowserRouter
} from "react-router-dom";
const root=document.getElementById('root');
ReactDOM.createRoot(root).render(
<HashRouter>
{/* 你的(de)應(ying)用程序的(de)其余部分在這(zhe)里 */}
</HashRouter>,
);
<BrowserRouter>
使用干凈的 URL
將當前位(wei)置存儲在瀏(liu)覽(lan)器的地址欄中,并使用瀏(liu)覽(lan)器的內置歷史堆棧進行導航
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
HashRouter
} from "react-router-dom";
const root=document.getElementById('root');
ReactDOM.createRoot(root).render(
<HashRouter>
{/* 你的應用程序的其(qi)余部分在這里 */}
</HashRouter>,
);
<HashRouter>
用于 Web 瀏覽(lan)器(qi),因為某些(xie)原因不應(或不能)將 URL 發送到服務(wu)器(qi)
import * as React from "react";
import {
NativeRouter
} from "react-router-native";
function App() {
return (
<NativeRouter>
{/* 你的應用程序(xu)的其(qi)余部分在這(zhe)里 */}
</NativeRouter>
);
}
<NativeRouter>
是在 React Native 應用程序中運行 React Router 的(de)推薦接口
import * as React from "react";
import { create } from "react-test-renderer";
import {
MemoryRouter,
Routes,
Route,
} from "react-router-dom";
describe("My app", () => {
it("renders correctly", () => {
let renderer = create(
<MemoryRouter initialEntries={["/users/mjackson"]}>
<Routes>
<Route path="users" element={<Users />}>
<Route path=":id" element={<UserProfile />} />
</Route>
</Routes>
</MemoryRouter>
);
expect(renderer.toJSON()).toMatchSnapshot();
});
});
<MemoryRouter>
在內部將其位置存儲在一個數組中。 與 <BrowserHistory>
和 <HashHistory>
不(bu)同,它不(bu)依賴于(yu)外部源,例(li)如瀏覽器中的(de)歷史堆棧(zhan)。 這使得它非常適合(he)需(xu)要完全(quan)控制歷史堆棧(zhan)的(de)場景,例(li)如測試
<Router>
是所有路由器組件(如 <BrowserRouter>
和 <StaticRouter>
)共享的低級接口。 就 React 而言,<Router>
是一個上下文(wen)提供者,它向(xiang)應(ying)用(yong)程序(xu)的其(qi)余部分提供路由信(xin)息
import * as React from "react";
import * as ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router-dom/server";
import http from "http";
function requestHandler(req, res) {
let html = ReactDOMServer.renderToString(
<StaticRouter location={req.url}>
{/* 你的應用程(cheng)序的其余部分(fen)在(zai)這里 */}
</StaticRouter>
);
res.write(html);
res.end();
}
http.createServer(requestHandler)
.listen(3000);