5.ReactRouter路由与Redux状态管理
一、ReactRouter 路由
1、ReactRouter 基础路由介绍
- 安装 ReactRouter
npm i react-router-dom
- 在/src/router/index.ts中创建路由配置
//createBrowserRouter: history模式 createBrowserRouter: hash模式
import { createBrowserRouter } from "react-router-dom";
import type { RouteObject } from "react-router-dom";
import Home from "../views/Home";
const router = createBrowserRouter([
  {
    path: "/",
    element: <Home />,
  },
]);
export default router;
- 在/src/index.tsx中配置路由
路由生效组件
<RouterProvider router={router} />
import { RouterProvider } from "react-router-dom";
import router from "./router";
const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);
- /src/views/Home/Home.tsx
import React from "react";
const Home = () => {
  return <div>Home</div>;
};
export default Home;
2、React Router v6 的嵌套路由
- 在src/views/Layout/Layout.tsx中创建布局组件,引入Outlet组件
import React from "react";
// Outlet组件用于显示子路由
import { Outlet } from "react-router-dom";
import Home from "../Home";
import User from "../User";
const Layout = () => {
  return (
    <div>
      <div className="slider">菜单</div>
      <div className="content">
        <Outlet />
      </div>
    </div>
  );
};
export default Layout;
- 新建src/views/User/User.tsx
import React from "react";
const User = () => {
  return <div>User</div>;
};
export default User;
- 在src/router/index.ts中配置子路由
import { createBrowserRouter } from "react-router-dom";
import type { RouteObject } from "react-router-dom";
import Home from "../views/Home";
import User from "../views/User";
import Layout from "../views/Layout";
const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      {
        path: "",
        element: <Home />,
      },
      {
        path: "user",
        element: <User />,
      },
    ],
  },
]);
export default router;
3、路由表组件写法
- src/router/index.ts
import {
  Route,
  createRoutesFromElements,
  createBrowserRouter,
} from "react-router-dom";
import Home from "../views/Home/Home";
import About from "../views/About/About";
import App from "../App";
const routes = createRoutesFromElements(
  <Route path="/" element={<App />}>
    <Route path="" element={<Home />}></Route>
    <Route path="about" element={<About />}></Route>
  </Route>
);
const router = createBrowserRouter(routes);
export default router;
4、声明式路由,路由跳转<Link to="" />
- src/views/Layout/Layout.tsx
import React from "react";
import { Link, Outlet } from "react-router-dom";
import Home from "../Home";
import User from "../User";
const Layout = () => {
  return (
    <div>
      <div className="slider">菜单</div>
      <div className="content">
        <Outlet />
        <Link to="/">Home</Link>
        <Link to="/user">User</Link>
      </div>
    </div>
  );
};
export default Layout;
二、动态路由模式模式与编程式路由模式
1、动态路由:path:/user/:id与useParams函数
- src/router/index.js
import { createBrowserRouter } from "react-router-dom";
import App from "../App.jsx";
import User from "../views/User/User";
import UserInfo from "../views/UserInfo/UserInfo";
// 路由表
const routes = [
  {
    path: "/",
    element: <App />,
    children: [
      {
        path: "user",
        element: <User />,
        children: [
          {
            path: "userinfo/:id", // 动态路由
            element: <UserInfo />,
          },
        ],
      },
    ],
  },
];
const router = createBrowserRouter(routes);
export default router;
- src/views/User/User.jsx
import "./User.scss";
import { Outlet, Link } from "react-router-dom";
export default function User() {
  return (
    <>
      <div></div>
      <Link to="/user/userinfo/123">userInfo123</Link> ||
      <Link to="/user/userinfo/456">userInfo456</Link>
      <Outlet />
    </>
  );
}
- src/views/UserInfo/UserInfo.jsx
import "./UserInfo.scss";
import { useParams } from "react-router-dom";
export default function UserInfo() {
  // 获取动态路由参数
  const params = useParams();
  return <div>UserInfo,{params.id}</div>;
}
2、带样式的声明式路由 NavLink
NavLink 组件默认会添加active类名,样式需要自己添加
- src/views/User/User.jsx
import "./User.scss";
import { Outlet, NavLink } from "react-router-dom";
export default function User() {
  return (
    <>
      <div></div>
      <NavLink to="/user/userinfo/123">userInfo123</NavLink> ||
      <NavLink to="/user/userinfo/456">userInfo456</NavLink>
      <Outlet />
    </>
  );
}
- src/views/User/User.scss
.active {
  color: aqua;
}
如果需要默认设置选择和未选中样式,可以在NavLink组件上设置activeClassName和activeStyle属性
- src/views/User/User.jsx
import React from "react";
import "./User.scss";
import { Outlet, NavLink } from "react-router-dom";
export default function User() {
  return (
    <>
      <div></div>
      <NavLink
        className={({ isActive }) => (isActive ? "curSelect1" : "none")}
        to="/user/userinfo/123"
      >
        userInfo123
      </NavLink>
      ||
      <NavLink
        className={({ isActive }) => (isActive ? "curSelect2" : "none")}
        to="/user/userinfo/456"
      >
        userInfo456
      </NavLink>
      <Outlet />
    </>
  );
}
- src/views/User/User.scss
.curSelect1 {
  color: aqua;
}
.curSelect2 {
  color: pink;
}
3、编程式路由跳转useNavigate
useNavigate 可以在组件内进行路由跳转
- src/views/User/User.jsx
import React from "react";
import "./User.scss";
import { Outlet, useNavigate } from "react-router-dom";
export default function User() {
  const navigate = useNavigate();
  const handleClick = () => {
    navigate("/user/userinfo/123");
  };
  return (
    <>
      <div></div>
      <button onClick={handleClick}>click to userinfo</button>
      <Outlet />
    </>
  );
}
三、useLocation与 useSearchParams 
1、useLocation 可以获取当前路由信息
其中有location.hash表示当前路由的哈希值,location.pathname表示当前路由的路径名,location.search表示当前路由的查询参数,location.state表示当前路由的额外信息,location.key表示当前路由的 key
src/views/User/User.jsx
import React from "react";
import "./User.scss";
import { Outlet, useNavigate } from "react-router-dom";
export default function User() {
  const navigate = useNavigate();
  const handleClick = () => {
    navigate("/user/userinfo/12?34#56", { state: { foo: 789 } });
  };
  return (
    <>
      <div></div>
      <button onClick={handleClick}>click to userinfo</button>
      <Outlet />
    </>
  );
}
在src/views/UserInfo/UserInfo.jsx中使用 useLocation
import "./UserInfo.scss";
import { useLocation } from "react-router-dom";
export default function UserInfo() {
  const location = useLocation();
  console.log(location, "useLocation");
  return <div>UserInfo</div>;
}
显示的结果是
{
  hash: "#56",
  pathname: "/user/userinfo/123",
  search: "?34",
  state: { foo: 789 },
  key: "63o4wd7k"
}
2、useSearchParams 可以获取当前路由的查询参数,如获取?username=xiaomu中的xiaomu
src/views/User/User.jsx
import React from "react";
import "./User.scss";
import { Outlet, useNavigate } from "react-router-dom";
export default function User() {
  const navigate = useNavigate();
  const handleClick = () => {
    navigate("/user/userinfo/123?username=xiaobu");
  };
  return (
    <>
      <div></div>
      <button onClick={handleClick}>click to userinfo</button>
      <Outlet />
    </>
  );
}
在src/views/UserInfo/UserInfo.jsx中使用useSearchParams,通过searchParams.get('username')获取和通过setSearchParams({age:18})更改当前路由的查询参数
import "./UserInfo.scss";
import { useSearchParams } from "react-router-dom";
export default function UserInfo() {
  const [searchParams, setSearchParams] = useSearchParams();
  console.log(searchParams.get("username"));
  const handleClick = () => {
    setSearchParams({ age: 18 });
  };
  return (
    <>
      <div>UserInfo</div>
      <button onClick={handleClick}>修改username</button>
    </>
  );
}
四、默认路由展示与重定向路由与 404 处理
1、默认路由展示
默认路由:{index:true,element:
在src/views/About/About.jsx通过Outlet展示路由
import React from "react";
import "./About.scss";
import { Outlet } from "react-router-dom";
export default function About() {
  return (
    <>
      <div>About</div>
      <Outlet />
    </>
  );
}
import { createBrowserRouter } from "react-router-dom";
import App from "../App.jsx";
import Home from "../views/Home/Home";
import About from "../views/About/About";
const routes = [
  {
    path: "/",
    element: <App />,
    children: [
      {
        path: "/home",
        element: <Home />,
      },
      {
        path: "/about",
        element: <About />,
        // 默认路由展示
        children: [
          {
            index: true,
            element: <div>默认页面展示</div>,
          },
        ],
      },
    ],
  },
];
const router = createBrowserRouter(routes);
export default router;
2、重定向路由
重定向路由:{index:true,element:}
3、404 处理
errorElement:
const routes = [
  {
    path: "/",
    element: <App />,
    errorElement: <div>404</div>,
  },
];
若是只在某一个页面下进行 404 处理,可以这样写
const routes = [
  {
    path: "/",
    element: <App />,
    children: [
      {
        path: "/home",
        element: <Home />,
      },
      {
        path: "/about",
        element: <About />,
        children: [
          {
            path:'*'
            element: <div>404</div>,
          },
        ],
      },
    ],
  },
];
五、loader 函数与 redirect 方法
1、loader 函数
- loader 函数进行路由前触发,配合 redirect 做权限拦截
- useLoaderData()获取 loader 函数返回的数据
在src/router/index.js中使用 loader 函数
const routes = [
  {
    path: "/",
    element: <App />,
    errorElement: <div>404</div>,
    children: [
      {
        path: "/home",
        element: <Home />,
        // loader 函数进行路由前触发
        loader: async () => {
          console.log("loader");
          // 经过一秒后返回数据
          let res = await new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve({ errorcode: 0 });
            }, 1000);
          });
          return res;
        },
      },
    ],
  },
];
在useLoaderData中获取 loader 函数返回的数据
import React from "react";
import "./Home.scss";
import { useLoaderData } from "react-router-dom";
export default function Home() {
  const data = useLoaderData();
  console.log(data); // {errorcode:0}
  return <div>Home</div>;
}
2、redirect 方法
redirect 方法进行路由后触发,配合 loader 做权限拦截
- 创建一个 - BeforeEach包裹根组件
- 自定义元信息 - meta
- 组件中获取 - meta信息:- matchRoutes与- useLocation
src/components/BeforeEach/BeforeEach.jsx
import React from "react";
import { useLocation, matchRoutes, Navigate } from "react-router-dom";
import { routes } from "../../router";
export default function BeforeEach(props) {
  const location = useLocation();
  const matchs = matchRoutes(routes, location);
  // 获取当前路由的元信息
  const { auth } = matchs[matchs.length - 1].route.meta;
  if (auth) {
    return <Navigate to="/login" />;
  } else {
    return <div>{props.children}</div>;
  }
}
在src/router/index.js中使用 BeforeEach 组件
import { createBrowserRouter } from "react-router-dom";
import App from "../App.jsx";
import Home from "../views/Home/Home";
import Login from "../views/Login/Login";
import About from "../views/About/About";
import BeforeEach from "../components/BeforeEach/BeforeEach";
// 路由表
export const routes = [
  {
    path: "/",
    element: (
      <BeforeEach>
        <App />
      </BeforeEach>
    ),
    errorElement: <div>404</div>,
    children: [
      {
        path: "/",
        element: <Home />,
        meta: { title: "首页", auth: false },
      },
      {
        path: "/login",
        element: <Login />,
        meta: { title: "登录", auth: false },
      },
      {
        path: "/about",
        element: <About />,
        meta: { title: "关于", auth: true },
      },
    ],
  },
];
const router = createBrowserRouter(routes);
export default router;
