React中項(xiàng)目路由配置與跳轉(zhuǎn)方法詳解
最近使用 React
和 TS
搭建了一個(gè)項(xiàng)目,組件庫(kù)使用的是 Antd5
。
在配置路由的過(guò)程中出現(xiàn)了一些小問(wèn)題,所以記錄一下這個(gè)過(guò)程。
路由配置集中管理
首先,安裝 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> );
接下來(lái),在 src/routes
目錄下創(chuàng)建 index.tsx
文件,引入所需依賴,并將組件映射到對(duì)應(yīng)路由上,讓 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()
方法實(shí)現(xiàn)路由組件懶加載。
路由懶加載就是把不同的路由組件分割成不同的代碼塊,只有在路由被訪問(wèn)的時(shí)候,才會(huì)加載對(duì)應(yīng)組件,其實(shí)就是按需加載。使用方法如下:
() => import(`@/views/${component}`)
如果在某個(gè)路由下有子路由,通過(guò) children
屬性配置,就是上面代碼中的這段:
const routes: Array<RouteType> = [ // ... { path: '/personal', element: <><Outlet></>, children: [ { path: 'information', element: <Information /> }, { path: 'contacts', element: <Contacts /> } ] } ];
其中,我們需要在渲染子組件的位置使用 Outlet
組件占位,當(dāng)然也可以使用 useOutlet()
,它會(huì)返回該路由組件的子路由元素。
由于我使用 Fragment
作為子組件的容器,所以直接把 Outlet
放在這里占位。
一般情況下,父組件中還會(huì)有其他元素,此時(shí)我們需要這樣設(shè)置:
import { Outlet } from 'react-router-dom'; export const Information = () => { return ( <Content> // ... <Outlet /> // 子組件渲染出口 </Content> ) }
最后,使用 useRoutes()
動(dòng)態(tài)配置路由。
useRoutes()
是 React Router V6 的一個(gè) Hook,它接收一個(gè)路由數(shù)組(也就是 routes
數(shù)組),根據(jù)匹配到的路由渲染相應(yīng)的組件。
import { useRoutes } from 'react-router-dom'; // 引入 useRoutes // ... const WrappedRoutes = () => { return useRoutes(routes); }; export default WrappedRoutes;
到此,路由的集中管理就配置完畢。我們?cè)?app.tsx
文件中引入 WrapperRoutes
就可以使用了。
import WrappedRoutes from '@/router/index'; const App: React.FC = () => { return ( <Layout style={{ minHeight: '100vh' }}> <Sider> <div style={{ height: 90 }} /> <Menu /> // 側(cè)邊導(dǎo)航欄 </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
也可以實(shí)現(xiàn):
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 /> // 側(cè)邊導(dǎo)航欄 </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 中的嵌套路由可以只定義相對(duì)父路由的相對(duì)路徑,內(nèi)部會(huì)為我們自動(dòng)拼接全路徑。
Menu 組件實(shí)現(xiàn)路由跳轉(zhuǎn)
導(dǎo)航菜單使用的是 Menu
組件。
首先,定義一個(gè)類型為 Array<MenuItem>
的 menuList
,用于導(dǎo)航菜單的展示。
// ... import WrappedRoutes from '@/router/index'; // 引入路由表 type MenuItem = { label: string, key: string, icon?: React.ReactNode children?: Array<MenuItem> } const menuList: Array<MenuItem> = [{ label: "首頁(yè)", key: "/index", icon: <PieChartOutlined rev={undefined} />, }, { label: "個(gè)人辦公", key: "/personal", icon: <PushpinOutlined rev={undefined} />, children: [ { label: "個(gè)人信息", 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;
此時(shí)點(diǎn)擊菜單項(xiàng)不能跳轉(zhuǎn)到對(duì)應(yīng)的路由,怎么實(shí)現(xiàn)呢?
在 Menu 組件上可以綁定點(diǎn)擊事件,通過(guò)它可以獲取 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} /> ) }
點(diǎn)擊 “個(gè)人信息” 菜單項(xiàng),可以看到,key
屬性就是我們要跳轉(zhuǎn)到的路由組件對(duì)應(yīng)的 url
。
接下來(lái),我們?cè)?handleClick
函數(shù)中使用 useNavigate()
,將當(dāng)前 event
對(duì)象的 key
屬性傳入,即可根據(jù)傳入的 url
實(shí)現(xiàn)路由跳轉(zhuǎn)。
同時(shí),為了在全局環(huán)境下保持當(dāng)前選項(xiàng)的激活狀態(tài),需要使用 useLocation()
實(shí)時(shí)獲取并保存當(dāng)前 url
,并交給 Menu
組件的 selectedKeys
屬性,它就是用于保存當(dāng)前激活的菜單選項(xiàng)。
import { useLocation, useNavigate } from 'react-router-dom'; const App = () => { const navigate = useNavigate(); const { pathname } = useLocation(); // 獲取當(dāng)前url const handleClick = (e: any) => { navigate(e.key); // 實(shí)現(xiàn)跳轉(zhuǎn) } return ( // ... <Menu theme="dark" selectedKeys={[pathname]} mode="inline" items={menuList} onClick={handleClick} /> ) }
除去組件庫(kù)的引入,這部分功能的完整代碼如下:
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: "首頁(yè)", key: "/index", icon: <PieChartOutlined rev={undefined} />, }, { label: "個(gè)人辦公", key: "/personal", icon: <PushpinOutlined rev={undefined} />, children: [ { label: "個(gè)人信息", 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(); // 獲取當(dāng)前url const handleClick = (e: any) => { // console.log(e); // console.log('key', e.key); navigate(e.key); // 實(shí)現(xiàn)跳轉(zhuǎn) } 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 組件動(dòng)態(tài)切換
首先,根據(jù)上面配置的 menuList
實(shí)現(xiàn)一個(gè) getBreadcrumbNameMap
函數(shù),生成 key
和 label
的映射關(guān)系。
export const breadcrumbMap: Map<string, string> = new Map(); // 因?yàn)樽勇酚勺疃嘀挥幸粚?,使用雙循環(huán)簡(jiǎn)單實(shí)現(xiàn)功能。 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);
我們已經(jīng)使用 useLocation
保存了當(dāng)前路由組件對(duì)應(yīng)的 url
,也就是 pathname
。
將得到的 pathname
以 /
字符分割并逐節(jié)遍歷,在 breadcrumbMap
中尋找對(duì)應(yīng)的 label
,找到了就拼接下一段,繼續(xù)在 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> ) }
最后即可成功獲取當(dāng)前路由對(duì)應(yīng)的面包屑導(dǎo)航,效果如上。
以上就是React中項(xiàng)目路由配置與跳轉(zhuǎn)方法詳解的詳細(xì)內(nèi)容,更多關(guān)于React路由配置的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用webpack5從0到1搭建一個(gè)react項(xiàng)目的實(shí)現(xiàn)步驟
這篇文章主要介紹了使用webpack5從0到1搭建一個(gè)react項(xiàng)目的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12React高級(jí)指引之Refs and the DOM使用時(shí)機(jī)詳解
在典型的React數(shù)據(jù)流中,props是父組件與子組件交互的唯一方式。要修改一個(gè)子組件,你需要使用新的props來(lái)重新渲染它。但是,在某些情況下,你需要在典型數(shù)據(jù)流之外強(qiáng)制修改子組件2023-02-02解決React報(bào)錯(cuò)React.Children.only expected to rece
這篇文章主要為大家介紹了React報(bào)錯(cuò)React.Children.only expected to receive single React element child分析解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01React?Hooks的useState、useRef使用小結(jié)
React Hooks 是 React 16.8 版本引入的新特性,useState和useRef是兩個(gè)常用的Hooks,本文主要介紹了React?Hooks的useState、useRef使用,感興趣的可以了解一下2024-01-01