React中项目路由配置与跳转方法详解
作者:寄给江南
最近使用 React
和 TS
搭建了一个项目,组件库使用的是 Antd5
。
在配置路由的过程中出现了一些小问题,所以记录一下这个过程。
路由配置集中管理
首先,安装 react-router-dom
包:
npm i react-router-dom
在 main.tsx
中,引入 BrowserRouter
并包裹在 App
组件外层:
import ReactDOM from 'react-dom/client'; import { BrowserRouter } from "react-router-dom"; import App from './App.tsx'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( <BrowserRouter> <App /> </BrowserRouter> );
接下来,在 src/routes
目录下创建 index.tsx
文件,引入所需依赖,并将组件映射到对应路由上,让 Router
知道在哪里渲染它们,如下:
import { lazy } from "react"; import { Outlet } from 'react-router-dom'; export interface RouteType { path: string; element: React.ReactNode; children?: Array<RouteType>; } const Index = lazy(() => import('@/views/Index/index')); const Information = lazy(() => import('@/views/personal/Information/index')); const Contacts = lazy(() => import('@/views/personal/Contacts/index')); const routes: Array<RouteType> = [ { path: '/index', element: <Index />, } { path: '/personal', element: <><Outlet></>, children: [ { path: 'information', element: <Information /> }, { path: 'contacts', element: <Contacts /> } ] } ]; export default routes;
在上述代码中,使用 React.lazy()
方法实现路由组件懒加载。
路由懒加载就是把不同的路由组件分割成不同的代码块,只有在路由被访问的时候,才会加载对应组件,其实就是按需加载。使用方法如下:
() => import(`@/views/${component}`)
如果在某个路由下有子路由,通过 children
属性配置,就是上面代码中的这段:
const routes: Array<RouteType> = [ // ... { path: '/personal', element: <><Outlet></>, children: [ { path: 'information', element: <Information /> }, { path: 'contacts', element: <Contacts /> } ] } ];
其中,我们需要在渲染子组件的位置使用 Outlet
组件占位,当然也可以使用 useOutlet()
,它会返回该路由组件的子路由元素。
由于我使用 Fragment
作为子组件的容器,所以直接把 Outlet
放在这里占位。
一般情况下,父组件中还会有其他元素,此时我们需要这样设置:
import { Outlet } from 'react-router-dom'; export const Information = () => { return ( <Content> // ... <Outlet /> // 子组件渲染出口 </Content> ) }
最后,使用 useRoutes()
动态配置路由。
useRoutes()
是 React Router V6 的一个 Hook,它接收一个路由数组(也就是 routes
数组),根据匹配到的路由渲染相应的组件。
import { useRoutes } from 'react-router-dom'; // 引入 useRoutes // ... const WrappedRoutes = () => { return useRoutes(routes); }; export default WrappedRoutes;
到此,路由的集中管理就配置完毕。我们在 app.tsx
文件中引入 WrapperRoutes
就可以使用了。
import WrappedRoutes from '@/router/index'; const App: React.FC = () => { return ( <Layout style={{ minHeight: '100vh' }}> <Sider> <div style={{ height: 90 }} /> <Menu /> // 侧边导航栏 </Sider> <Layout> <Content style={{ margin: 16 }}> <WrappedRoutes /> // 渲染路由组件的位置 </Content> </Layout> </Layout> ) };
/routes/index.tsx
完整代码如下:
import { lazy } from "react"; import { Outlet, useRoutes } from 'react-router-dom'; export interface RouteType { path: string; element: React.ReactNode; children?: Array<RouteType>; } const Index = lazy(() => import('@/views/Index/index')); const Information = lazy(() => import('@/views/personal/Information/index')); const Contacts = lazy(() => import('@/views/personal/Contacts/index')); const routes: Array<RouteType> = [ { path: '/index', element: <Index />, } { path: '/personal', element: <><Outlet></>, children: [ { path: 'information', element: <Information /> }, { path: 'contacts', element: <Contacts /> } ] } ]; const WrappedRoutes = () => { return useRoutes(routes); }; export default WrappedRoutes;
如果不使用 useRoutes()
,上述功能使用 Routes
和 Route
也可以实现:
import { lazy } from "react"; import { Routes, Route, Navigate, Outlet } from 'react-router-dom'; const Index = lazy(() => import('@/views/Index/index')); const Information = lazy(() => import('@/views/personal/Information/index')); const Contacts = lazy(() => import('@/views/personal/Contacts/index')); // ... const App: React.FC = () => { return ( <Layout style={{ minHeight: '100vh' }}> <Sider> <div style={{ height: 90 }} /> <Menu /> // 侧边导航栏 </Sider> <Layout> <Content style={{ margin: 16 }}> // 渲染路由组件的位置,用 Routes 包裹 <Routes> <Route path="/" element={<Navigate to="/index" />} /> <Route path="index" element={<Index />} /> <Route path="personal" element={<><Outlet /></>} > <Route path="information" element={<Information />} /> <Route path="contacts" element={<Contacts />} /> </Route> </Routes> </Content> </Layout> </Layout> ) }
注意:V6 中的嵌套路由可以只定义相对父路由的相对路径,内部会为我们自动拼接全路径。
Menu 组件实现路由跳转
导航菜单使用的是 Menu
组件。
首先,定义一个类型为 Array<MenuItem>
的 menuList
,用于导航菜单的展示。
// ... import WrappedRoutes from '@/router/index'; // 引入路由表 type MenuItem = { label: string, key: string, icon?: React.ReactNode children?: Array<MenuItem> } const menuList: Array<MenuItem> = [{ label: "首页", key: "/index", icon: <PieChartOutlined rev={undefined} />, }, { label: "个人办公", key: "/personal", icon: <PushpinOutlined rev={undefined} />, children: [ { label: "个人信息", icon: <IdcardOutlined rev={undefined} />, key: "/personal/information" }, { label: "通讯录", icon: <ContactsOutlined rev={undefined} />, key: "/personal/contacts" } ] }] const App: React.FC = () => { return ( <Layout style={{ minHeight: '100vh' }}> <Sider> <div style={{ height: 90 }} /> <Menu theme="dark" mode="inline" items={menuList} /> </Sider> <Layout> <Content style={{ margin: 16 }}> <WrappedRoutes /> </Content> </Layout> </Layout> ) }; export default App;
此时点击菜单项不能跳转到对应的路由,怎么实现呢?
在 Menu 组件上可以绑定点击事件,通过它可以获取 key、keyPath 等属性。
const App = () => { const handleClick = (e: any) => { console.log(e); console.log('key', e.key); } return ( // ... <Menu theme="dark" mode="inline" items={menuList} onClick={handleClick} /> ) }
点击 “个人信息” 菜单项,可以看到,key
属性就是我们要跳转到的路由组件对应的 url
。
接下来,我们在 handleClick
函数中使用 useNavigate()
,将当前 event
对象的 key
属性传入,即可根据传入的 url
实现路由跳转。
同时,为了在全局环境下保持当前选项的激活状态,需要使用 useLocation()
实时获取并保存当前 url
,并交给 Menu
组件的 selectedKeys
属性,它就是用于保存当前激活的菜单选项。
import { useLocation, useNavigate } from 'react-router-dom'; const App = () => { const navigate = useNavigate(); const { pathname } = useLocation(); // 获取当前url const handleClick = (e: any) => { navigate(e.key); // 实现跳转 } return ( // ... <Menu theme="dark" selectedKeys={[pathname]} mode="inline" items={menuList} onClick={handleClick} /> ) }
除去组件库的引入,这部分功能的完整代码如下:
import WrappedRoutes from '@/router/index'; // 引入路由表 import { useLocation, useNavigate } from 'react-router-dom'; type MenuItem = { label: string, key: string, icon?: React.ReactNode children?: Array<MenuItem> } const menuList: Array<MenuItem> = [{ label: "首页", key: "/index", icon: <PieChartOutlined rev={undefined} />, }, { label: "个人办公", key: "/personal", icon: <PushpinOutlined rev={undefined} />, children: [ { label: "个人信息", icon: <IdcardOutlined rev={undefined} />, key: "/personal/information" }, { label: "通讯录", icon: <ContactsOutlined rev={undefined} />, key: "/personal/contacts" } ] }] const App: React.FC = () => { const navigate = useNavigate(); const { pathname } = useLocation(); // 获取当前url const handleClick = (e: any) => { // console.log(e); // console.log('key', e.key); navigate(e.key); // 实现跳转 } return ( <Layout style={{ minHeight: '100vh' }}> <Sider> <div style={{ height: 90 }} /> <Menu theme="dark" selectedKeys={[pathname]} mode="inline" items={menuList} onClick={handleClick} /> </Sider> <Layout> <Content style={{ margin: 16 }}> <WrappedRoutes /> </Content> </Layout> </Layout> ) }; export default App;
Breadcrumb 组件动态切换
首先,根据上面配置的 menuList
实现一个 getBreadcrumbNameMap
函数,生成 key
和 label
的映射关系。
export const breadcrumbMap: Map<string, string> = new Map(); // 因为子路由最多只有一层,使用双循环简单实现功能。 function getBreadcrumbNameMap(breadcrumbMap: Map<string, string>) { for (let menuItem of menuList) { breadcrumbMap.set(menuItem.key, menuItem.label); if (menuItem.children) { for (let child of menuItem.children) { breadcrumbMap.set(child.key, child.label); } } } } console.log(breadcrumbMap);
我们已经使用 useLocation
保存了当前路由组件对应的 url
,也就是 pathname
。
将得到的 pathname
以 /
字符分割并逐节遍历,在 breadcrumbMap
中寻找对应的 label
,找到了就拼接下一段,继续在 breadcrumbMap
中匹配 label
,以此类推,直到遍历完成。
const App = () => { const { pathname } = useLocation(); const pathSnippets = pathname.split('/').filter((i) => i); const breadcrumbItems = pathSnippets.map((_, index) => { const url = `/${pathSnippets.slice(0, index + 1).join('/')}`; console.log(url); return { key: url, title: <Link to={url}>{breadcrumbMap.get(url)}</Link>, }; }); return ( // ... <Content style={{ margin: 16, padding: 12, minHeight: '95vh', backgroundColor: '#fff' }}> <Breadcrumb style={{ marginBottom: 20 }} separator="/" items={breadcrumbItems} /> <WrappedRoutes /> </Content> ) }
最后即可成功获取当前路由对应的面包屑导航,效果如上。
以上就是React中项目路由配置与跳转方法详解的详细内容,更多关于React路由配置的资料请关注脚本之家其它相关文章!