如何在 React 中轻松路由?使用 React Router!
目录
开始
import React from "react";
import ReactDOM from "react-dom/client";
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
import Root from "./routes/root";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
root 路由比较特殊。
Not Found
import ErrorPage from "./error-page";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
**errorElement: <ErrorPage />,**
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
嵌套路由
让 <Contact />
渲染在 <Root />
的 children
(<Outlet />
) 中:
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
**children**: [
{
path: "contacts/:contactId",
element: <Contact />,
},
],
},
]);
使用 Layout 也能解决。
注意,必须在 Root 中原来 children 的地方替换或加上 Outlet:
客户端路由
使用 Link 组件。
该路由不会重新请求页面,完全由客户端操控
import { **Link** } from "react-router-dom";
export default function Root() {
return (
<>
<div id="sidebar">
{/* other elements */}
<nav>
<ul>
<li>
**<Link to={`contacts/1`}>Your Name</Link>**
</li>
<li>
**<Link to={`contacts/2`}>Your Friend</Link>**
</li>
</ul>
</nav>
{/* other elements */}
</div>
</>
);
}
加载数据
将数据导入路由组件,支持异步获取
// ./routes/root.jsx 数据获取方法
import { getContacts } from "../contacts";
export async function loader() {
const contacts = await getContacts();
return { contacts };
}
// main.jsx 数据导入 loader
import Root, { loader as rootLoader } from "./routes/root";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
**loader: rootLoader,**
children: [
{
path: "contacts/:contactId",
element: <Contact />,
},
],
},
]);
// ./routes/root.jsx 如何使用导入的数据?
import {
**useLoaderData**,
} from "react-router-dom";
/* other code */
export default function Root() {
...
const { contacts } = useLoaderData();
...
}
拦截 Form 提交
有时候表单还是有用的~
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}`);
}
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,**
},
],
},
]);
重定向
在组件中不起作用,组件中应使用 <link>
进行客户端路由而不是重定向!
或者编程式路由,用 navigate("/login")
。
import {
**redirect**,
} from "react-router-dom";
import { getContacts, createContact } from "../contacts";
export async function action() {
const contact = await createContact();
return **redirect(`/contacts/${contact.id}/edit`);**
}
激活 active 链接的样式
使用 NavLink 。
import {
**NavLink**,
} from "react-router-dom";
export default function Root() {
return (
<>
<div id="sidebar">
{/* other code */}
<nav>
<NavLink
to={`contacts/${contact.id}`}
**className={({ isActive, isPending }) =>
isActive
? "active"
: isPending
? "pending"
: ""
}**
>
{/* other code */}
</NavLink>
</nav>
</div>
</>
);
}
全局等待 Pending UI
当用户浏览应用程序时,React Router将保留旧页面,因为数据正在加载到下一个页面。你可能已经注意到,当你在列表之间点击时,应用程序感觉有点没有反应。让我们为用户提供一些反馈,这样应用程序就不会感到没有响应。
使用 useNavigation 钩子。
import {
// existing code
useNavigation,
} from "react-router-dom";
// existing code
export default function Root() {
const { contacts } = useLoaderData();
**const navigation = useNavigation();**
return (
<>
<div id="sidebar">{/* existing code */}</div>
<div
id="detail"
**className={
navigation.state === "loading" ? "loading" : ""
}**
>
<Outlet />
</div>
</>
);
}
索引 Index 路由
当一条路由有子路由,并且您位于父路由的路径上时,
import Index from "./routes/index";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{ index: true, element: <Index /> },
/* existing routes */
],
},
]);
浏览器后退
在编辑页面上,我们有一个取消按钮,它还没有做任何事情。我们希望它能做与浏览器的后退按钮相同的事情。
使用 useNavigation 钩子。
import {
**useNavigate**,
} from "react-router-dom";
export default function EditContact() {
const { contact } = useLoaderData();
const navigate = useNavigate();
return (
<Form method="post" id="contact-form">
{/* existing code */}
<p>
<button type="submit">Save</button>
<button
type="button"
**onClick={() => {
navigate(-1);
}}**
>
Cancel
</button>
</p>
</Form>
);
}
JSX 风格路由
import {
createRoutesFromElements,
createBrowserRouter,
} from "react-router-dom";
const router = createBrowserRouter(
createRoutesFromElements(
<Route
path="/"
element={<Root />}
loader={rootLoader}
action={rootAction}
errorElement={<ErrorPage />}
>
<Route errorElement={<ErrorPage />}>
<Route index element={<Index />} />
<Route
path="contacts/:contactId"
element={<Contact />}
loader={contactLoader}
action={contactAction}
/>
<Route
path="contacts/:contactId/edit"
element={<EditContact />}
loader={contactLoader}
action={editAction}
/>
<Route
path="contacts/:contactId/destroy"
action={destroyAction}
/>
</Route>
</Route>
)
);
Form 相关
获取 get 中的 url params
将 form 改为 Form,使用 Router 组件。
然后:
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();**
...
<input
id="q"
aria-label="Search contacts"
placeholder="Search"
type="search"
name="q"
**defaultValue={q}**
/>
表单变化时就提交
使用 useSubmit 钩子。
// existing code
import {
// existing code
**useSubmit,**
} from "react-router-dom";
export default function Root() {
const { contacts, q } = useLoaderData();
const navigation = useNavigation();
**const submit = useSubmit();**
return (
<>
<div id="sidebar">
<h1>React Router Contacts</h1>
<div>
<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>
{/* existing code */}
</div>
{/* existing code */}
</div>
{/* existing code */}
</>
);
}
减少历史记录?replace not push!
onChange={(event) => {
const isFirstSearch = q == null;
**submit(event.currentTarget.form, {
replace: !isFirstSearch,
});**
}}
导航没变化而数据有变
例如点击收藏按钮
使用 useFetcher 钩子。
const fetcher = useFetcher();
<fetcher.Form method="post">
</fetcher.Form>
常见场景
登录
按钮外包裹客户端路由组件
<**Link** to="/login">
<Button type="primary">登录</Button>
</**Link**>
当前路由(路径)?
获取当前路径用 useLocation 钩子。
const location = **useLocation()**
.
location.pathname
组件(声明)式路由
使用 <Link>
或 <NavLink>
组件
编程(命令)式路由
使用 useNavigate 钩子。
const navigate = useNavigate(); navigate('/home')
路径参数
使用 useParams 钩子。
<BrowserRouter>
<Routes>
<Route path='/foo/:id' element={Foo} />
</Routes>
</BrowserRouter>
import { useParams } from 'react-router-dom';
export default function Foo(){
**const params = useParams();**
return (
<div>
<h1>{params.id}</h1>
</div>
)
}
参考文章
致谢: