React-Router基础应用(含v6.4+ 数据加载)

353 阅读3分钟

安装

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注册路由,需要BrowserRouterRoutes包裹
  • 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;
  • NavLinkLink十分类似,区别就是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提供的useSearchParamshook去获取参数

返回一个数组(与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">&larr; 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

loaderaction中可能会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或者页面的场景,例如弹出框或者动态表单。