React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React路由配置

React中项目路由配置与跳转方法详解

作者:寄给江南

这篇文章主要为大家详细介绍了React中项目路由配置与跳转方法的相关资料,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以了解一下

最近使用 ReactTS 搭建了一个项目,组件库使用的是 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(),上述功能使用 RoutesRoute 也可以实现:

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 函数,生成 keylabel 的映射关系。

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路由配置的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文