2024.05.20更新:最新的React Router v6的路由懒加载配置方式,可以查看这篇文章:
以下为原文:
升级到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>
)
}
结果报错了!
所以,其实是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!