升react-router v6后,react-router-config不能用了?——react-router v6实现集中式路由

7,320 阅读4分钟

2024.05.20更新:最新的React Router v6的路由懒加载配置方式,可以查看这篇文章:

React Router v6 路由懒加载的2种方式

以下为原文:

升级到react-router-dom v6之后,然后使用react-router-config去配置集中式路由,会发现工程报这个错了:
'Switch' is not exported from 'react-router-dom'....

原来react-router-dom v6这个版本更新了大部分api,已经不兼容react-router-config了,但是v6版本react-router-dom本身就增加了配置集中式路由的支持,下面来看下怎么修改。

先简单介绍一下v6版本发生了哪些改变

一、v6的改变

0、<Route>路径的变化

  • 占位符 * 和 :id可以用,正则不能用了
  • v6中的所有路径匹配都将忽略URL上的尾部"/"
// v6
/user/*
/detail/:id

1、<Switch>重命名为<Routes>

只是变了个名称

// 原
<Switch>
    <Route path="/index1"><Index1/></Route>
    <Route path="/index2"><Index1/></Route>
</Switch>

// v6
<Routes>
    <Route path="/index1"><Index1/></Route>
    <Route path="/index2"><Index1/></Route>
</Routes>

2、<Route>的component变成了element

// 原
<Route path="/index1" component={Index1} />

// v6
<Route path="/index1" element={<Index1 />} />

3、<Outlet>渲染子路由

用来渲染子路由,我理解类似于props.children和react-router-config的renderRoutes,或者vue的<router-view>

<BrowserRouter>
  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/content" element={<Content />}>
      <Route path="index1" element={<Index1 />} />
      <Route path="index2" element={<Index2 />} />
    </Route>
  </Routes>
</BrowserRouter>

// Content
import { Outlet } from 'react-router-dom'

function Content() {
  return (
    <div>
      <title>这是Content</title>
      
      {/* 这里渲染子路由!! */}
      <Outlet />
      {/* 这里渲染子路由!! end */}
    </div>
  )
}

4. useHistory没了,用useNavigate作为替代,页面跳转写法改变

// 原
import { useHistory } from 'react-router-dom';
...
const history = useHistory()
history.push('/index1')
history.replace('/index2')
...


// v6
import { useNavigate } from 'react-router-dom';
...
const navigate = useNavigate();
navigate('/index1')
navigate('/index2', {replace: true})
...

5、<Redirect/>没了,新增<Navigate/>替代

用法感觉没什么区别。。。

<Redirect to='index1' />
<Navigate to='index1' />

6、重头戏来了,新增了useRoutes,可以替代react-router-config

通过useRoutes渲染路由,传入我们已经集中配置好的routes

const routes = {
    path: '/',
    element: <SecurityLayout />,
    children: [
        { path: '', element: <Navigate to="/user/login" /> }, // Redirect 重定向!
        {
            path: '',
            element: <BasicLayout />,
            children: [
                // BasicLayout 业务页面
                {
                    path: 'index1',
                    element: <Index1/>
                },
                {
                    path: 'index2',
                    element: <Index2/>
                },
            ]
        },
    ]
}
function RenderRoutes() {
    const element = useRoutes(routes)
    return element;
}

其实react-router v6应该还有其它很多改动,但是目前涉及到代码改动比较多的就是这几个特性,所以这篇文章仅介绍这几个

二、使用useRoutes实现react-router-config需要做的修改

了解和旧版本的不同后,我们就在代码层面进行对应的修改

1、修改routes

  • 父级的路由后面要加上 *,进行匹配
  • 子路由不必带着父级路由的path,例:只需要写'login'就行,不用写成'user/login'
  • component改成element
  • 子路由的key从'routes'改成'children'
  • 如果有重定向的,使用element: <Navigate />
import Login from '@/pages/login'

const routes = [
  // UserLayout
  {
    path: 'user/*',
    element: <UserLayout />,
    children: [
      { path: '', element: <Navigate to="login" /> }, // Redirect
      {
        path: 'login',
        element: <Login />
      },
      route404
    ]
  },
]

2、App.jsx中, useRoutes要放在function App的外面

其实准确的说是,useRoutes所在的组件,要在<Router>的内部

看到useRoutes的介绍之后,我一开始是这么写的:

import { HashRouter, useRoutes } from 'react-router-dom'
import routes from '@/router'

function App() {
  const element = useRoutes(routes)
  return (
    <HashRouter>
      <ContextProvider>
        {element}
      </ContextProvider>
    </HashRouter>
  )
}

结果报错了!

image.png

所以,其实是useRoutes所在的组件,必须在<Router>中,所以改成这样就好了:

import { HashRouter, useRoutes } from 'react-router-dom'
import routes from '@/router'
// 渲染路由
function RouteElement() {
  const element = useRoutes(routes)
  return element
}
function App() {
  return (
    <HashRouter>
      <ContextProvider>
        <RouteElement />
      </ContextProvider>
    </HashRouter>
  )
}

3、父级组件中渲染子路由使用<Outlet>

// 原v5
import { renderRoutes } from 'react-router-config'
import routes from './router.config'

function renderChildren(o) { // 可能是props或者routes。props:props.route.routes
  let routes = []
  if (Object.prototype.toString.call(o) === '[object Array]') {
    routes = o
  } else if (Object.prototype.toString.call(o) === '[object Object]') {
    routes = o.route?.routes || []
  }

  return renderRoutes(routes)
}

// UserLayout 使用react-router-config的renderRoutes去渲染
function Index(props) {
  return (
    <div className={styles.container}>
      <div className={styles.content}>
        {renderChildren(props.route.routes)} // 这里渲染子路由
      </div>
      <Footer />
    </div>
  )
}
// v6 直接使用Outlet就行了
// UserLayout 
import React from 'react'
import Footer from '@/components/layout/Footer'
import styles from './index.less'
import { Outlet } from 'react-router-dom' // Outlet用于渲染children
function Index() {
  return (
    <div className={styles.container}>
      <div className={styles.content}>
        <Outlet />    // 这里渲染子路由
      </div>
      <Footer />
    </div>
  )
}

4、组件中用到useHistory的地方都要替换成useNavigate

5、v6中React.lazy的实现

这些修改之后,各自对应的功能都没问题了,但是发现原来的React.lazy不好用了,先看下原来的写法:

// 原v5

// routes里
const routes = [
  ...
  {
    path: '/user/login',
    component: React.lazy(() => import('../pages/login')) // 路由懒加载
  },
  ...
]

// Suspense放在了App.jsx里,相当于是路由的最外层
function App() {
    return (
        <HashRouter>
            <Suspense fallback={<LazyLoading />}>
                <ContextProvider>{renderChildren(routes)}</ContextProvider>
            </Suspense>
        </HashRouter>
    );
}

v6中,Suspense要放在路由的element上:

// v6 

// routes里
const Login_lazy = lazy(() => import('@/pages/login'))  // 路由懒加载

const routes = [
  ...
  {
    path: 'login',
    element: (
        <Suspense fallback={<LazyLoading />}>  // 这里包裹Suspense
          <Login_lazy />
        </Suspense>
    )
  },
  ...
]

// 删除App.jsx里的Suspense
function App() {
    return (
        <HashRouter>
            <ContextProvider>
                <RouteElement />
            </ContextProvider>
        </HashRouter>
    );
}

这样react-router v6的路由懒加载就可以用了
routes这样写的话,每个element都要写一个Suspense,我们可以简化一下:

const routes = [
  ...
  // BasicLayout 业务页面
  {
    path: 'index1',
    element: () => import('@/pages/index1')
  },
  {
    path: 'index2',
    element: () => import('@/pages/index2')
  },
  ...
]

function LazyElement(props) {
  const { importFunc } = props
  const LazyComponent = lazy(importFunc)
  return (
    <Suspense fallback={<div>路由懒加载...</div>}>
      <LazyComponent />
    </Suspense>
  )
}

// 处理routes 如果element是懒加载,要包裹Suspense
function dealRoutes(routesArr) {
  if (routesArr && Array.isArray(routesArr) && routesArr.length > 0) {
    routesArr.forEach((route) => {
      if (route.element && typeof route.element == 'function') {
        const importFunc = route.element
        route.element = <LazyElement importFunc={importFunc} />
      }
      if (route.children) {
        dealRoutes(route.children)
      }
    })
  }
}
dealRoutes(routes)

export default routes

这样看着更像以前的写法,同时更简洁了

至此,react-router v6的集中配置路由就完成了

ok done!