基于 react-router 嵌套路由与配置化路由
React Router嵌套路由与配置化路由
文章导航
最近重新回顾了一下 react-router
,学到了一些以前没有注意到和没有弄明白的问题。
🔴 本文基于
react-router-dom 5.2.0
,6.x
版本存在较大API改动,不完全适用。
React Router 的基本用法
在最开始的时候,最好是先跟着官方的文档或是一些优秀的博客学习基本的用法,推荐下面两篇:
🔖 React Router 中文文档
🔖 阮一峰 React Router
React Router与其他路由组件
相关组件 | 区别 |
---|---|
react-router | 核心功能。包含通用功能和通用 Hooks |
react-router-dom | 基于 react-router 添加了浏览器运行环境的一些组件和功能。 |
react-router-native | 适用于 React Native |
react-router-redux | React Router 和 Redux 的集成。 |
react-router-config | 提供可配置化的路由 |
🥬 注意:
react-router
和react-router-dom
使用的时候不需要都引入,一般会选择使用react-router-dom
。
嵌套路由
有时候需要实现一些在一个页面中需要展示其他的页面的情况。比如下面的路由关系:
一个 Music
网站,在 主 页面进行 推荐/排行 页面导航,并且需要保留 Home
的导航栏,也就是说 推荐/排行 嵌入到 Home
主页中。
文件目录结构参考:
新建组件
🔖 Recommendation.js
// src/views/Recommendation/Recommendation.js
import React, { Component } from 'react';
// 内容推荐页面
class Recommendation extends Component {
render () {
return ( <div> Recommendation Page </div> );
}
}
export default Recommendation;
🔖 Ranking.js
// src/views/Ranking/Ranking.js
// Ranking 和 Recommendation 页面很相似
import React, { Component } from 'react';
// 排行页
class Ranking extends Component {
render () {
return ( <div> Ranking Page </div> );
}
}
export default Ranking;
引入组件并创建路由
🔖 Home.js
// src/views/Home/Home.js
import React, { Component, Fragment } from 'react';
import { Layout } from "antd"
import { Route, Link } from 'react-router-dom';
import Recommendation from '../Recommendation/Recommendation';
import Ranking from '../Ranking/Ranking';
import styles from "./index.module.css"
const { Header, Content, Footer } = Layout
class Home extends Component {
render () {
return (
<Fragment>
<Header className={styles.header}>
<Link to="/recommendation">Recommendation </Link>
<Link to="/ranking"> Ranking</Link>
</Header>
<Content className={styles.content}>
{/*我们想要把子页面渲染在 Content 中,所以响应的路由就要放在Content中,这样在路由匹配到 /recommendation时,就会先加载父组件Home,在切换的时候也只会替换 Route 的部分,保留了 Home 页的内容 */}
<Route path="/recommendation" component={Recommendation} />
<Route path="/ranking" component={Ranking} />
</Content>
<Footer className={styles.footer}>CopyRight</Footer>
</Fragment>
);
}
}
export default Home;
🔖 App.js
// src/App.js
import React from "react";
import { BrowserRouter, Route } from "react-router-dom"
import Home from './views/Home/Home';
import "./App.css"
function App () {
return (
<BrowserRouter>
<Route path="/" component={Home} />
</BrowserRouter>
);
}
export default App;
实现效果
实现的效果就是下面的样子:
上面的路由全部使用的都是 一级路由,似乎看不出来组件之间的关系,当然我们也可以将上面的子路由改成
<Route path="/home/recommendation" component={Recommendation} />
<Route path="/home/ranking" component={Ranking} />
不过要注意的是:这种写法如果在父路由中开启 exact 匹配,就会导致子组件加载不出来呢。所以建议子路由使用 exact
父路由不要使用。
关于 exact
exact
是 Route
的精准匹配模式,这种模式下,/
就不能匹配 /home
。此外,使用 exact
会导致一个问题,就是子组件加载不出来。
封装可配置化路由
将路由封装成可配置化可以将所有的路由放到一个文件中,更便于管理(封装的思路也就是上面的实现方式,只不过是换一种方式将路由集中起来而已。)
添加路由配置文件
🔖 router.js
// src/config/router.js
import Home from "../views/Home/Home"
import Recommendation from "../views/Recommendation/Recommendation"
import Ranking from "../views/Ranking/Ranking"
// 将项目中的路由关系配置成数组
/**
* path 匹配的路径,就是 Route 的 path
* component 要渲染的组件(这里先采用上面导入方式,后面进行lazy()懒加载优化)
* children 需要在当前页面渲染的子路由,也是个数组和外层路由结构基本一致
*/
const routerConfig = [{
path: "/",
component: Home,
children: [{
path: "/recommendation",
component: Recommendation
}, {
path: "/ranking",
component: Ranking
}]
}]
export default routerConfig
实现Route函数式渲染
🔖 util.js
// src/util/util.js
import { Route } from "react-router-dom";
// 根据路由配置实现 Route 渲染
// 这里使用了箭头函数, 省去了很多的 return
/** 这里返回的其实就是要渲染的 Route 列表,大概是像这样子(示意)
* [<Route/>, <Route/>...]
* 就是用下面的函数代替了手动写 Route
*/
export const renderRoutes = (routerConfig) =>
// 将需要用到的属性component,children解构出来,其他直接根据配置渲染到 Route 上
(routerConfig || []).map(({ component: Component, children, ...routeProps }) =>
/* render() 是component={} 的替代写法,
* 这里使用render进行渲染是为了将 当前路由的子路由 children 绑定到 routes 属性,
* 这样子元素的props终究会出现 routes,也就是当前组建的子路由,再合适的位置进行`渲染`就可以了
*/
<Route {...routeProps} render={(props) => <Component {...props} routes={children} />} />
)
修改子路由渲染方式
然后在 App.js
和 Home.js
(需要渲染子路由的组件) 中修改之前的手动渲染为 renderRoutes
函数渲染:
🔖 App.js
// src/App.js
import React from "react";
import { BrowserRouter, Route } from "react-router-dom"
import { renderRoutes } from "./util/util"
import routerConfig from "./config/router"
import "./App.css"
function App () {
return (
<BrowserRouter>
{/* <Route path="/" component={Home} /> */}
// 修改为函数式渲染
{renderRoutes(routerConfig)}
</BrowserRouter>
);
}
export default App;
🔖 Home.js
// src/views/Home/Home.js
import React, { Component, Fragment } from 'react';
import { Layout } from "antd"
import { Route, Link } from 'react-router-dom';
import Recommendation from '../Recommendation/Recommendation';
import Ranking from '../Ranking/Ranking';
import styles from "./index.module.css"
import { renderRoutes } from '../../util/util';
const { Header, Content, Footer } = Layout
class Home extends Component {
render () {
return (
<Fragment>
<Header className={styles.header}>
<Link to="/home/recommendation">Recommendation </Link>
<Link to="/home/ranking"> Ranking</Link>
</Header>
<Content className={styles.content}>
{/* <Route path="/home/recommendation" component={Recommendation} />
<Route path="/home/ranking" component={Ranking} /> */}
{renderRoutes(this.props.routes || [])}
</Content>
<Footer className={styles.footer}>CopyRight</Footer>
</Fragment>
);
}
}
export default Home;
Recommendation
和 Ranking
组件是没有子组件的,不需要路由渲染。
完成上面的改造之后,你会发现实现效果上是一模一样的 😂
优化
组件懒加载
自定义组件懒加载组件
在之前的 router.js
中是通过 import module from 'file'
的方式来引入组件页面的,默认会全部载入所有页面,会对性能有一定的影响,如果实现用到的时候再进行加载组件就能解决这个问题。
import()
默认导入的方式,默认返回 Promise
,并且只支持默认的导出。我们需要实现自定义懒加载组件使其需要渲染的时候再加载。
React.lazy()
✅ React Document - React.lazy()
React.lazy()
和Suspense
是React
官方提供的组件懒加载解决方案。
React.lazy()
是懒加载的一种方式,参数为一个函数,返回 Promise
。懒加载只支持默认的导入,如果需要重命名则需要进行中间操作。
Suspense
就是用来解决懒加载带来的等待问题的,在组件没有加载完成之前是没办法渲染的,使用 Suspense
可以等待组件加载完成之后触发重新渲染。
两种方式的原理是相似的。
1. 自定义异步加载组件
异步加载组件的组件
🔖 asyncLoadComponent.js
// src/util/asyncLoadComponent.js
import { Component } from "react"
const asyncLoadComponent = (loadComponent) => class AsyncComponent extends Component {
constructor(props) {
super(props)
this.state = { component: null }
}
componentDidMount () {
loadComponent()
.then(res => res).then(res => {
this.setState({ component: res.default || res })
})
}
render () {
const { component: Component } = this.state
//注意: 这里一定要把 props 传下去 (里面包含了子路由 routes 信息, 不传递的话会导致子路由无法渲染哦)
return Component ? <Component {...this.props} /> : <div>loading</div>
}
}
修改组件加载方式
🔖 router.js
// src/config/router.js
const routerConfig = [{
path: "/",
component: asyncLoadComponent(() => import("../views/Home/Home")),
// exact: true,
children: [{
path: "/home/recommendation",
component: asyncLoadComponent(() => import("../views/Recommendation/Recommendation"))
}, {
path: "/home/ranking",
component: asyncLoadComponent(() => import("../views/Ranking/Ranking"))
}]
}]
2 使用 React.lazy() 和 Suspense
修改组件加载方式
🔖 router.js
// src/config/router.js
import { lazy } from "react"
const routerConfig = [{
path: "/",
component: lazy(() => import("../views/Home/Home")),
// exact: true, 不能开启exact
children: [{
path: "/home/recommendation",
component: lazy(() => import("../views/Recommendation/Recommendation"))
}, {
path: "/home/ranking",
component: lazy(() => import("../views/Ranking/Ranking"))
}]
}]
export default routerConfig
添加 Suspense
🔖 App.js
// src/App.js
import React, { Suspense } from "react";
import { BrowserRouter } from "react-router-dom"
import { renderRoutes } from "./util/util"
import routerConfig from "./config/router"
import "./App.css"
function App () {
return (
// fallback 属性是组件加载时显示的内容,是必须的
<Suspense fallback={<div>Loading</div>}>
<BrowserRouter>
{/* <Route path="/" component={Home} /> */}
{renderRoutes(routerConfig)}
</BrowserRouter>
</Suspense>
);
}
export default App;
实现的效果就是下面的样子:组件加载未完成的时候出现 Loading
webpack 遇到 import() 时就会进行代码分割,这样就能将不同的组件分别打在不同的文件中,在需要的时候进行加载。
在 Network
中能看到两个页面是分别加载的:
当然,目前已经有框架为我们做这些了。像 Next.js
默认支持 约定式路由
,不再需要我们配置; Umi.js
支持 约定式路由
和 配置式路由
。除了路由这些优秀的框架还提供了代码分割、打包优化等更多的优化内容。
源代码
Gitee my-music
更多推荐
所有评论(0)