安装
npm install react-dom-router
注册路由
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import Homepage from "./pages/Homepage";
import AppLayout from "./pages/AppLayout";
import PageNotFound from "./components/PageNotFound";
function App() {
return (
<BrowserRouter>
<Routes>
<Route index element={<Homepage />} />
<Route path="app" element={<AppLayout />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
);
}
- 使用
Route
注册路由,需要BrowserRouter
和Routes
包裹 path
属性是路径,加/
是从根路径开始,不加/
则直接添加到父路径前element
是该路径需要渲染的组件或元素- 使用
index
替代path
时,直接在父路径(这里是/
)中渲染该组件(元素),即默认的子路由 path
为*
时是所有路由均匹配不到时渲染的组件(元素),一般是404页面
嵌套路由
嵌套路由只需在Route
标签内注册即可,需要与Outlet
配合使用
function App() {
return (
<Routes>
<Route path="/" element={<Dashboard />}>
<Route
path="messages"
element={<DashboardMessages />}
/>
<Route path="tasks" element={<DashboardTasks />} />
</Route>
</Routes>
);
}
Outlet
相当于Vue中的router-view
,当路径完全匹配时(这里是/task
)时,会在<Outlet />
的位置渲染指定组件(这里是<DashboardTasks />
)
import { Outlet } from "react-router-dom";
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Outlet />
</div>
);
}
NavLink 与 Link
import { NavLink } from "react-router-dom";
import styles from "./AppNav.module.css";
function AppNav() {
return (
<nav className={styles.nav}>
<ul>
<li>
<NavLink to="cities">Cities</NavLink>
</li>
<li>
<NavLink to="countries">Countries</NavLink>
</li>
</ul>
</nav>
);
}
export default AppNav;
NavLink
与Link
十分类似,区别就是NavLink
在与当前路径匹配的情况下下,会有一个active
属性,方便我们去书写样式to
属性是要跳转的路径
Navigate 与 useNavigate
渲染时,<Navigate />
元素会更改当前位置,一般用于进入父类由时默认跳转至子路由
<Route path="app" element={<AppLayout />}>
<Route index element={<Navigate replace to="cities" />} />
<Route
path="cities"
element={<CityList />}
/>
</Route>
- 在我们进入
app
路径时,会重定向至app/cities
路径 replace
属性允许我们回退至上一路径to
属性是我们要跳转的子路径
useNavigate
钩子返回一个函数,该函数允许我们使用编程方式进行导航(类似Vue中的router.push
)
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate(-1) //跳转至上一路径 (back)
navigate("form") //跳转至 `父路径/form`
使用Query参数
传入参数直接拼接进Url即可:?query1=XXX&query2=XXX
<Link
className={`${styles.cityItem} ${
currentCity.id === id ? styles["cityItem--active"] : ""
}`}
to={`${id}?lat=${position.lat}&lng=${position.lng}`}
/>
使用react-router提供的useSearchParams
hook去获取参数
返回一个数组(与useState
类似),用来获取和设置query参数
使用get('键名')
函数获取query参数的值
import { useSearchParams } from "react-router-dom";
export function useUrlPosition() {
const [searchParams,setSearchParams] = useSearchParams();
const lat = searchParams.get("lat");
const lng = searchParams.get("lng");
return [lat, lng];
}
React Router With Data Loading (v6.4+)
注册路由
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import Home from "./ui/Home";
import Menu, { loader as menuLoader } from "./features/menu/Menu";
import Cart from "./features/cart/Cart";
import CreateOrder, {
action as orderAction,
} from "./features/order/CreateOrder";
import Order, { loader as orderLoader } from "./features/order/Order";
import AppLayout from "./ui/AppLayout";
import Error from "./ui/Error";
import { action as updateOrderAction } from "./features/order/UpdateOrder";
const router = createBrowserRouter([
{
element: <AppLayout />,
errorElement: <Error />,
children: [
{
path: "/",
element: <Home />,
},
{
path: "/menu",
element: <Menu />,
loader: menuLoader,
errorElement: <Error />,
},
{
path: "/cart",
element: <Cart />,
},
{
path: "order/new",
element: <CreateOrder />,
action: orderAction,
},
{
path: "order/:orderId",
element: <Order />,
loader: orderLoader,
errorElement: <Error />,
action: updateOrderAction,
},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;
在v6.4+的写法中, 我们可以使用对象去注册路由, 使用<RouterProvider />
组件, 传入设置好的router
属性
path
: 路径children
: 子路由element
: 该路径下渲染的组件errorElement
: 错误处理loader
: 请求数据
布局路由
布局路由是编写路由的一个思路, 它的作用是提供一个公共的元素给它的子路由使用。它不需要匹配任何特定的路径,而是根据子路由的路径来渲染相应的元素(根据<Outlet/>
组件的位置)。
<AppLayout />
import { Outlet, useNavigation } from 'react-router-dom';
import CartOverview from '../features/cart/CartOverview';
import Header from './Header';
import Loader from './Loader';
function AppLayout() {
const navigation = useNavigation();
const isLoading = navigation.state === 'loading';
return (
<div className="grid h-screen grid-rows-[auto_1fr_auto]">
{isLoading && <Loader />}
<Header></Header>
<div className="overflow-auto">
<main className="mx-auto max-w-3xl">
<Outlet />
</main>
</div>
<CartOverview />
</div>
);
}
export default AppLayout;
错误处理:errorElement
React Router会捕获应用程序中的大多数错误(事件处理和useEffect中抛出的错误除外), 当一个错误被抛出时, element
中的组件将不再渲染, 而是使用errorElement
中的组件替代
如果一个路由没有errorElement
,这个错误会冒泡到最近的带有errorElement
的父路由上
也可以使用它来代替path:'*'
去匹配404页面
useRouteError
我们可以使用useRouteError
去获取抛出的错误信息
<Error />
import { useRouteError } from 'react-router-dom';
import LinkButton from './LinkButton';
function NotFound() {
const error = useRouteError();
return (
<div>
<h1>Something went wrong 😢</h1>
<p>{error.data || error.message}</p>
<LinkButton to="-1">← Go back</LinkButton>
</div>
);
}
export default NotFound;
数据请求: loader(GET)
在路由元素渲染之前向其提供数据。通常与(使用数据的)组件写在一起
params
: 提供动态路由参数
//路由
{
path: "order/:orderId",
element: <Order />,
loader: orderLoader,
}
//loader
function loader({ params }) {
//如果路径为'orderId/123456',则params.orderId=123456
const order = await getOrder(params.orderId);
return order;
}
useLoaderData
获取loader返回的数据
const order = useLoaderData();
useLoaderData
并不初始化fetch操作。它只是读取React Router内部管理的取回结果
这也意味着在渲染之间返回的数据是稳定的,所以你可以安全地将它传递给React钩子中的依赖数组,比如useEffect
可以在任何组件或自定义hook中使用这个钩子,而不仅仅是在Route
中。它将返回上下文最近的路由的数据。
数据更改: action(POST、PUT、PATCH、DELETE)
在应用程序向route发送非get请求时会被调用。通常与<Form>
一同使用
参数与loader相同
request
: 发送到路由的Fetch Request实例params
: 路由参数
export async function action({ request }) {
//解析FromData
const formData = await request.formData();
const data = Object.fromEntries(formData);
const order = {
...data,
cart: JSON.parse(data.cart),
priority: data.priority === "true",
};
const error = {};
if (!isValidPhone(order.phone))
error.phone =
"Please give us your correct phone number.We might need it to contact you.";
if (Object.keys(error).length > 0) return error;
const newOrder = await createOrder(order);
//不要过度使用 会导致性能问题
store.dispatch(clearCart());
return redirect(`/order/${newOrder.id}`);
}
useActionData
提供上一次navigation的action
结果的返回值。
例如上文action返回的error, 我们可以通过这个hook去获取
const formErrors = useActionData();
redirect
在loader
和action
中可能会return一个重定向操作, 但useNavigate
是hook,不能在常规函数中使用, 此时便可以使用redirect
完成重定向
import { redirect } from "react-router-dom";
return redirect(`/order/${newOrder.id}`);
<Form>
通常与action一同使用, 完成POST、PATCH、PUT、DELETE操作
method
:与普通HTML表单方法相同,只是它除了支持“get”和“post”之外,还支持“put”、“patch”和“delete”。默认值为“get”。action
:表单将被提交到的url,就像HTML表单操作一样。唯一的区别是默认操作。对于HTML表单,它默认为完整的URL。对于<Form>
,它默认为上下文中最近路由的相对URL。
请确保您的
<input/>
有name属性,否则FormData将不包括该字段的值。
useNavigation
通常用于获取数据加载的状态(state属性)
state的值有三个: idle,loading和submitting, 变化情况如下
- GET:
idle → loading → idle
- POST, PUT, PATCH, or DELETE:
idle → submitting → loading → idle
const navigation = useNavigation();
const isLoading = navigation.state === 'loading';
useFetcher
在不改变路由的情况下与服务器进行交互或调用该路由外的loader和action
fetcher.load
调用匹配路径的loader函数, 返回的数据存储在fetcher.data中
useEffect(
function () {
if (!fetcher.data && fetcher.state === "idle") fetcher.load("/menu");
},
[fetcher],
);
fetcher.state
与navigation的state相同:
- idle
- submitting
- loading
fetcher.Form
与<Form>
类似, 但无需在action中返回路由跳转, 即不会导致页面的跳转和刷新。
import { useFetcher } from "react-router-dom";
import Button from "../../ui/Button";
import { updateOrder } from "../../services/apiRestaurant";
function UpdateOrder({ order }) {
const fetcher = useFetcher();
return (
<fetcher.Form method="PATCH" className="text-right">
<Button>Make priority</Button>
</fetcher.Form>
);
}
export default UpdateOrder;
// eslint-disable-next-line
export async function action({ request, params }) {
// params--路由参数
const data = { priority: true };
await updateOrder(params.orderId, data);
return null;
}
总的来说,Form组件和fetcher.Form的区别是,Form组件会导致页面的跳转或刷新,而fetcher.Form不会。Form组件适合用于那些需要改变URL或者页面的场景,例如搜索或者登录。fetcher.Form适合用于那些不需要改变URL或者页面的场景,例如弹出框或者动态表单。