# React-Router 6
# React Router6 对比 React Router5
React-Router6对比React-router5,更像Vue-Router了
- 首先5的Route path都是绝对路径,不支持相对路径,而6则支持相对路径
Route组件属性
path:定义路由的路径,可以是字符串或字符串数组。例如,path="/users"或path={['/users', '/admins']}。element:指定路由对应的组件。例如,element={<Home />}。children:在路由匹配时渲染的子元素。与element属性互斥,不能同时使用。caseSensitive:指定路由路径是否区分大小写,默认为false。sensitive:指定路由是否对 URL 参数区分大小写,默认为false。mergeParams:在嵌套路由中,是否将父路由的参数合并到子路由中,默认为false。index:指定路由为默认路由,即没有匹配的路径时显示的组件。fallback:指定当路由匹配时渲染的后备组件(通常在路由懒加载时使用)。
其中没有5的component属性了,取而代之的是element属性
# Outlet组件
在 React Router 6 中,<Outlet> 组件用于渲染嵌套路由的子路由内容。它充当了父级路由组件的占位符,用于显示嵌套路由的子路由。
类似于Vue Router中的router-view
对应hooks 为 useOutlet,二者功能相同
# Link
即<router-link />
# 编程式导航
useNavigate
const navigate = useNavigate()
navigate("/b"); // 跳转
2
export declare function useNavigate(): NavigateFunction;
export interface NavigateFunction {
(to: To, options?: NavigateOptions): void;
(delta: number): void;
}
export interface NavigateOptions {
replace?: boolean;
state?: any;
preventScrollReset?: boolean;
relative?: RelativeRoutingType;
}
2
3
4
5
6
7
8
9
10
11
12
# Link和NavLink
<NavLink>是<Link>的一个特定版本,会在匹配上当前的url的时候给已经渲染的元素添加参数
<a href="/b">to b</a>
<a aria-current="page" class="active" href="/">a</a>
2
3
上面是Link渲染出来的,下面是NavLink渲染的,可以看到多出来一个class="active"
这样就可以设置默认激活,做上色等操作
aria-content (opens new window)是无障碍属性
# URL prarms传递
import { Routes, Route, useParams } from "react-router-dom";
function App() {
return (
<Routes>
<Route
path="invoices/:userid"
element={<Invoice />}
/>
</Routes>
);
}
function Invoice() {
let params = useParams();
return <h1>Invoice {params.userid}</h1>;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 嵌套路由
<Outlet /> 组件即 Vue-Router中的 router-view
function Header() {
return (
<ul className="navs">
<li className="nav">
<Link to="/invoices/1">to invoice 1</Link> |
<Link to="/invoices/2">to invoice 2</Link>
</li>
</ul>
);
}
function View() {
return (
<Routes>
<Route path="a" element={<Invoices />}></Route>
<Route path="b/:userid" element={<Invoice />}></Route>
<Route path="/invoices" element={<Invoices />}>
<Route path=":invoiceid" element={<Invoice />}></Route>
<Route path="2" element={<SendInvoice />}></Route>
</Route>
</Routes>
);
}
function Invoices() {
return (
<>
<h3>Invoices</h3>
<Outlet />
</>
);
}
function Invoice() {
const params = useParams();
return (
<>
<div>Invoice id</div>
<div>{params.invoiceid}</div>
</>
);
}
function SendInvoice() {
return <div>SendInvoice</div>;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# React Router6嵌套路由
# 1.使用Route组件嵌套实现
<Router>
<Suspense fallback={<div>loading...</div>}>
{/* <RouterProvider router={router} /> */}
<Routes>
<Route path="/" element={<Navigate to="/dashboard" />}></Route>
<Route path="/dashboard" element={"about"} />
<Route path="/settings" element={<div>settings <Outlet /></div>}>
{/* 这里为了解决重定向需要新增一条带index的路由 */}
<Route index element={<Navigate to='profile' />} />
<Route path="profile" element={"profile"} />
<Route path="profile2" element={"profile2"} />
</Route>
</Routes>
</Suspense>
</Router>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用这种方式能够很好的解决redirect重定向问题
# 2.使用createBrowserRouter创建配置式路由
const routes: RouteObject[] = [
{
path: "/",
element: <AppLayout />,
children: [
{ path: "", element: <span>/</span> },
{ path: "/about", element: <span>About</span> },
{ path: "test", element: <span>Test</span> },
{
path: "layout",
element: <Layout />,
children: [
{ path: "1", element: <span>layout1</span> },
{ path: "2", element: <span>layout2</span> },
{
path: "3",
element: <Layout3 />,
children: [
{ path: "1", element: <span>layout/3-1</span> },
{ path: "2", element: <span>layout/3-2</span> }
]
}
]
}
]
},
{ path: "/login", element: <span>Login</span> }
];
const router = createBrowserRouter(routes);
const App = () => <RouterProvider router={router} />
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const routes: RouteObject[] = []
function Index() {
const element = useRoutes(routes);
return element;
// 或者直接 return useRoutes(routes)
}
export default function App() {
return (
<div className="App">
{/* <RouterProvider router={router} /> */}
<Router>
<Index />
</Router>
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
==useRoutes() may be used only in the context of a <Router> component.==
useRoutes 必须在 <Router>组件的上下文中使用
RouterProvider对比useRoutes
RouterProvider 用于整个应用的静态路由配置,而 useRoutes 用于在特定组件内部进行动态路由匹配和渲染
# token无效时跳转登录页
# 定义高阶组件 Auth
实现类似于beforeEach功能的导航守卫
/**
* 路由守卫
* @description 验证用户token是否在有效期。
*/
const RouterAuth = ({ children }: { children: JSX.Element }) => {
const location = useLocation();
const token = useUser((state) => state.user.token);
if (!token) {
return <Navigate to="/login" state={location} replace />;
}
return children;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
而token则存储到全局store中,在axios拦截器中设置token清除token
service.interceptors.response.use(
(response) => {
if (response.data.errCode === 1004001) {
useUserStore.getState().setLoginStatus(false);
}
return response;
},
(error: AxiosError) => {
if (error.response?.status === 401 || error.response?.status === 500) {
message.error("登录凭证无效!");
useUserStore.getState().setToken('')
return Promise.reject((error.response.data as ResponseData).error);
}
return Promise.reject(error);
}
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 获取URL查询参数
在Vue-Router中我们可以通过$route.query获取URL查询参数,而在React-Router中没有对此进行封装,可以通过
useLocation拿到未序列化的查询参数类似于?name=zs&age=20这种格式
const { search } = useLocation()
const queryParams = Object.fromEntries(new URLSearchParams(location.search).entries())
// { name: 'zs', age: 20 }
2
3
封装一下
/**
* 获取序列化后的URL查询参数 ?a=1&b=2 返回{ a:1, b:2 }
* @returns
*/
export const useQueryParams = () =>
Object.fromEntries(new URLSearchParams(location.search).entries());
2
3
4
5
6
当TypeScript提示Property 'entries' does not exist on type 'URLSearchParams'.ts(2339)时,
这是因为tsconfig.json的lib字段缺少DOM.Iterable
使用Ts加上类型声明
type QueryParams<T> = { [K in keyof T]: string };
// 用Record代替
type QueryParams<T> = Record<keyof T, string>
/**
* 获取序列化后的URL查询参数 ?a=1&b=2 返回{ a:1, b:2 }
* @returns
*/
export const useQueryParams = <T>(): QueryParams<T> =>
Object.fromEntries(new URLSearchParams(location.search).entries()) as QueryParams<T>;
2
3
4
5
6
7
8
9
10
# React Router 6有类似于VueRouter的addRoute方法吗
在 React Router 中,路由配置是静态的,通常是在组件中声明的。
然而,你可以通过使用 useRoutes 钩子以编程的方式动态创建路由
const App = () => {
// 定义动态路由配置
const dynamicRoutes = {
path: '/',
element: <Home />, // 默认首页
children: [
{ path: 'about', element: <About /> },
// 可以根据需要添加更多的动态路由
],
};
// 使用 useRoutes 钩子将动态路由配置应用到实际路由中
const routeElement = useRoutes(dynamicRoutes);
return (
<Router>
<Routes>{routeElement}</Routes>
</Router>
);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# React Router6实现全局导航守卫BeforeEach
import { useContext } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { globalContext } from '../App';
import { rotuesMap } from '.';
type RouterBeforeEachProps = {
children: JSX.Element;
};
const RouterBeforeEach: React.FC<RouterBeforeEachProps> = ({ children }) => {
const { isLogin } = useContext(globalContext);
const location = useLocation();
const to = rotuesMap.get(location.pathname);
console.log('to: ', to);
// 如果未登录,且去的页面不是登录页,则重定向到登录页
if (!isLogin && to?.path !== '/login') {
return <Navigate to='/login' />;
}
// 如果已登录,不可直接去登录页,应通过logout退出登录来跳转登录页
if (to?.path === '/login' && isLogin) {
return <Navigate to='/' />;
}
// 需要鉴权的页面
if (to?.meta?.requireAuth && !isLogin) {
// 判断有没有这个页面的权限
// ...
// 没有则跳转至指定页
return <Navigate to='/unpermission' />;
}
if (to?.redirect) {
// 这里需要判断redirect是绝对路径,还是相对路径
if (to.redirect.startsWith('/')) {
return <Navigate to={to.redirect} />;
} else {
const redirect = `${to.fullPath}/${to.redirect}`.replace(/\/\//, '/');
return <Navigate to={redirect} />;
}
}
return children;
};
export default RouterBeforeEach;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
使用时给routes内需要拦截的路由element包起来即可
由于我这里使用的是嵌套路由,所以只要包第一层的路由即可
const routes: RouteConfig[] = [
{
path: '/login',
element: (
<RouterBeforeEach>
<Login />
</RouterBeforeEach>
)
},
{
path: '/',
errorElement: <Error />,
element: (
<RouterBeforeEach>
<AppLayout />
</RouterBeforeEach>
),
children: [
{ path: '', element: <span>/</span> },
{ path: 'about', element: <About /> },
{ path: 'test', element: <Test /> },
{
path: 'layout',
// element: <Layout />,
componentPath: 'pages/layout1/index',
children: [
{ path: '1', componentPath: 'pages/layout1-1/index' },
{ path: '2', componentPath: 'pages/layout1-2/index' },
{
path: '3',
componentPath: 'pages/layout1-3/index',
children: [
{ path: '1', componentPath: 'pages/layout1-3-1/index' },
{ path: '2', componentPath: 'pages/layout1-3-2/index' }
]
}
]
}
]
},
{ path: '*', element: <NotFound /> }
];
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
详细代码:https://github.com/fncheng/react-router6-guard/blob/master/src/router/index.tsx
# 如果使用Vue框架
<template>
<div :style="{ color: 'red' }">Here is the number: {{ number }}</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useNumber } from '../pages/useNumber'
const number = ref()
const getNumber = async () => {
const value = await useNumber()
number.value = value
}
getNumber()
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16