可以少去理解一些不必要的概念,而多去思考为什么会有这样的东西,它解决了什么问题,或者它的运行机制是什么?
1. 什么是路由
路由是根据不同的 URL 地址展示不同的页面、组件。一个针对 React 而设计的路由解决方案、可以友好的帮你解决组件到 URl(路径) 之间的同步映射关系。
V5 版本思想一切皆组件。
2. 路由安装 V5 - V6
npm install react-router-dom@5.3.0
npm install react-router-dom
3. 路由使用 V5 - V6
3.1 定义路由
V5:HashRouter\Route
HashRouter // 路由器 路径上就会带上 /#/ 号
Route // 路由
- Path 匹配路径
- component 组件
- exact 精确匹配,路由默认都是模糊匹配
import React, { Component } from 'react'
import { HashRouter, Route } from 'react-router-dom'
export default class IndexRouter extends Component {
render() {
return (
<HashRouter>
<Route path="/film" component={Film}/>
<Route path="/cinema" component={Cinema}/>
<Route path="/center" component={Center}/>
</HashRouter>
)
}
}
v6 Route 特性变更:
- path:匹配路径,当前页面对应的 URL 匹配。
- element:新增,用于决定路由匹配时,渲染那个组件。代替 v5 的 component 和 render。
3.2 重定向和匹配阻断
V5:Redirect\Switch V6:Routes
Redirect 即使使用了exact,外面还要嵌套 Switch 来用。v6 使用 Routes 代替 Switch,而且是强制使用的,不然会报错。
Redirect // 重定向
- from 当前路径
- to 定向路径
- exact 精确匹配
Switch // 阻断继续向下匹配
export default class IndexRouter extends Component {
render() {
return (
<HashRouter>
<Switch>
<Route path="/film" component={Film}/>
<Route path="/cinema" component={Cinema}/>
<Route path="/center" component={Center}/>
{/* 重定向 */}
<Redirect from='/' to='/film'/>
{/* 万能匹配 */}
<Route component={NotFount}/>
</Switch>
</HashRouter>
)
}
}
v6 index 默认路由:
index 用于嵌套路由,仅匹配父路径时,设置渲染的组件。
解决当嵌套路由有多个子路由但本身无法确认默认渲染哪个子路由的时候,可以增加index属性来指定默认路由。index 路由和其他路由不同的地方是它没有path属性,他和父路由共享同一个路径。
export default function MRouter() {
return (
<Routes>
{/* <Route path='/' element={<Films/>}/> */}
<Route index element={<Films/>}/>
<Route path='/films' element={<Films/>}/>
<Route path='/cinema' element={<Cinema/>}/>
<Route path='/center' element={<Center/>}/>
<Route path='*' element={<Navigate to="/films"/>}/>
</Routes>
)
}
v6 Navigate 替代重定向组件 Redirect:'*'
是万能匹配
export default function MRouter() {
return (
<Routes>
{/* <Route path='/' element={<Films/>}/> */}
{/* <Route index element={<Films/>}/> */}
<Route path='/films' element={<Films/>}/>
<Route path='/cinema' element={<Cinema/>}/>
<Route path='/center' element={<Center/>}/>
<Route path='*' element={<Navigate to="/films"/>}/>
</Routes>
)
}
v6 自定义重定向组件 Redirect:
<Route path='*' element={<Redirect to="/films"/>}/>
import React, { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
export default function Redirect({to}) {
const navigate = useNavigate()
useEffect(()=>{
navigate(to, {replace:true})
})
return null
}
3.3 万能匹配和精确匹配
{/* 重定向 */}
<Redirect from='/' to='/film' exact/>
{/* 万能匹配 */}
<Route component={NotFount}/>
V6:'*'
是万能匹配:
<Route path='*' element={<Navigate to="/films"/>}/>
》路由匹配模式:https://blog.youkuaiyun.com/m0_45877477/article/details/126077383
3.4 嵌套路由
v5 需要在组件里添加嵌套路由。
export default function Film() {
return (
<div>
<div>Film</div>
<Switch>
<Route path='/film/isplaying' component={IsPlaying} />
<Route path='/film/noplaying' component={NoPlaying} />
<Redirect from='/film' to='/film/isplaying'/>
</Switch>
</div>
)
}
v6 新增路由容器 让嵌套路由更简单。
{/* 嵌套路由 */}
<Route path='/films' element={<Films/>}>
{/* <Route index element={<Nowplaying/>}/> */}
<Route path='' element={<Redirect to="/films/nowplaying"/>}/>
<Route path='comeingsoon' element={<Comeingsoon/>}/>
{/* 可以省略绝对路径,使用相对路径 */}
<Route path='/films/nowplaying' element={<Nowplaying/>}/>
</Route>
// 使用路由容器占位
import React from 'react'
import { Outlet } from 'react-router-dom'
export default function Films() {
return (
<div>
<div style={{height:"200px", background:"yellow"}}>大轮播</div>
{/* 路由容器 */}
<Outlet></Outlet>
</div>
)
}
3.5 路由跳转(导航)
-
声明式导航,就是 a 链接形式,v6 移除了 的 activeClassName 和 activeStyle。v6 新增了一个 Link 组件。NavLink 包含所有 Link 组件功能,新增了一个高亮的功能。
》a 链接形式、NavLink、Link 组件(原理也是 a 链接)
// a 链接方式,需要配合原生 js。 <a href = "/index.html">films</a> <a href = "#/film">films</a> // react 路由提供的组件,必须在路由中使用 <NavLink to="/film" activeclassName="active">films</NavLinks> <NavLink to="/center" activeclassName="active">films</NavLinks> // v6: <NavLink to='/films' className={({isActive})=>isActive?"kwactive":""}> 电影 </NavLink>
-
v5 编程式导航,就是 location.href 形式。
// 原生 js 方法 window.location.href = "#/detail" // react 路由提供的方法 - props 属性,Route 父组件传递过来的 - props 中 history 包含路由操作的方法 this.props.history.push("/detail/${id}") // 路由封装的 Hooks 函数,代替 props.history var history = useHistory() history.push("/detail/${id}")
-
v6 编程式导航:使用 useNavigate 代替 useHistory。
const navigate = useNavigate() // query(URL)传参 navigate(`/detail?id=${id}`) // 获取参数 const [searchParams, setSearchParams] = useSearchParams() searchParams.get("id") // 判断参数是否存在 searchParams.has('id') // 同时页面内也可以用 set 方法来改变路由 setSearchParams({"id" :2})
3.6 动态路由
通过设置动态路由可以在页面、组件之间传递参数。:id 是一个占位符,动态传值。
<Route path="/detail/:id" component={Center}/>
// 获取值:
var id = props.match.params.id
// react 路由提供的方法
- props 属性,Route 父组件传递过来的
- history 路由操作的方法集合
- match 匹配值的集合
- location 匹配值的集合
v6 动态路由:/:id
{/* 动态路由 */}
<Route path='/detail/:id' element={<Detail/>}/>
// 动态路由跳转传参
navigate(`/detail/${id}`)
// 接收动态路由参数
import { useParams } from 'react-router-dom'
const params = useParams()
console.log(params.id)
3.7 路由页面传参
-
动态路由传参。
<Route path="/detail/:id" component={Center}/> // 动态路由 this.props.history.push("/detail/${id}") // 路由导航 this.props.match.params.id // 参数值获取
-
query 传参(普通路由)。
刷新页面会导致参数丢失,比如分享一个详情页给别人打开,会导致没有参数打不开。
<Route path="/detail" component={Center}/> this.props.history.push({ pathName: '/detail', query: { id: '111' }}) // 路由导航 this.props.location.query.id // 参数值获取
-
state 传参(普通路由)。
刷新页面会导致参数丢失,比如分享一个详情页给别人打开,会导致没有参数打不开。
<Route path="/detail" component={Center}/> // 动态路由 this.props.history.push({ pathName: '/detail', state: { id: '111' }}) // 路由导航 this.props.location.state.id // 参数值获取
3.8 路由拦截/守卫
》render
v5 路由拦截/守卫:
<Route path="/login" component={Login}/>
{/* 路由拦截/守卫逻辑可以写在回调函数里 */}
<Route path="/center" render={(props)=>{ // 这里需要显式将属性传递给 Center 组件
return 是否授权 ? <Center {...props}/> : <Redirect to="/login"/>
}}/>
v6 路由拦截/守卫,必须封装成在组件里使用,不然不会更新:
{/* 路由拦截/守卫 */}
{/* <Route path='/center' element={isAuth()?<Center/>:<Redirect to='/login'/>}/> */} // 错误写法
<Route path='/center' element={<AuthComponent>
<Center></Center>
</AuthComponent>}/>
function isAuth() {
return localStorage.getItem("token")
}
// 路由拦截组件的封装
function AuthComponent({ children }) {
const isLogin = localStorage.getItem("token")
return isLogin?children:<Redirect to="/login"></Redirect>
}
3.9 路由模式
-
HashRouter 路由器模式,哈希模式。
-
BrowserRouter 路由器模式,浏览器模式。
BrowserRouter 没有 # 的路径,格式看起来更好。在 BrowserRouter 模式下如果发起一个请求,是真正朝后端发送请求要页面,如果后端没有对应的路径处理逻辑,就会 404。怎么解决这个问题?需要和后端确认一旦接收到不合法的路径,希望后端重新渲染我们(打包部署提供的)的首页 index.js。所谓的后端就是用 nginx 去配置一下就行了。而 HashRouter 不需要向后端发送请求要页面,自己管自己。
3.10 withRouter 高阶组件
通过包装一层,为跨级拿不到 props - history match location 的组件,提供 props - history 相关属性。即成为 withRouter 的孩子,干爹组件。
const WithComponent = withRouter(Component)
v6 中使用 withRouter 一定是你的某个组件使用类组件开发的,不支持钩子 useNavigate
。需要自己封装 withRouter。
import React from 'react'
import { useNavigate } from 'react-router-dom'
export default function withRouter(Conponent) {
return function(props){
const push = useNavigate()
return <Conponent { ...props } history={{push}}></Conponent>
}
}
// v5 中 withRouter 用法: withRouter(component)
3.11 路由懒加载(性能问题)
单页面应用路由过多时候导致首页渲染过慢,造成性能问题。需要使用 React 懒加载机制封装路由懒加载来优化。
// 路由懒加载
const LazyLoad = (path) => {
const Comp = React.lazy(()=>import(`../views/${path}`))
return (
<React.Suspense fallback={ <>加载中...</> }>
<Comp/>
</React.Suspense>
)
}
<Route path='/cinema' element={LazyLoad('Cinema')}/>
4. TS 中路由使用注意点
4.1 声明文档
如果出现无法找到模块的声明文件,可以到 @type 模块下下载声明文档。
5. v6 对比 v5
- v6 压缩包要比 v5 小一半;
-
特性变更:
- path:匹配路径,当前页面对应的 URL 匹配;
- element:新增,用于决定路由匹配时,渲染那个组件。代替 v5 的 component 和 render;
-
代替了
而且是强制使用的,不然会报错; - 路由容器让嵌套路由更简单;
- 导航 useNatigate 代替了 useHistory;
- 移除了 的 activeClassName 和 activeStyle;
- 钩子 useRoutes 代替了 react-router-config,即钩子配置路由;
6. v6 useRoutes 钩子(配置式路由)
配置式路由会根据配置数组生成对应的路由。
import React from 'react'
import { useRoutes } from 'react-router-dom'
import Redirect from '../components/Redirect'
export default function MRouter() {
const element = useRoutes([
{
path: "/films",
element: LazyLoad("Films"),
children: [
{
path: "",
element: <Redirect to="/films/nowplaying"/>
},
{
path: "nowplaying",
element: LazyLoad("films/Nowplaying")
},
{
path: "comeingsoon",
element: LazyLoad("films/Comeingsoon")
}
]
},
{
path: "/cinema",
element: LazyLoad("Cinema")
},
{
path: "/center",
element: <AuthComponent>
{ LazyLoad("Center") }
</AuthComponent>
},
{
path: "/login",
element: LazyLoad("Login")
},
{
path: "/detail/:myid",
element: LazyLoad("Detail")
},
{
path: "/",
element: <Redirect to="/films"/>
},
{
path: "*",
element: LazyLoad("NotFound")
}
])
return (
element
)
}
function isAuth() {
return localStorage.getItem("token")
}
function AuthComponent({ children }) {
const isLogin = localStorage.getItem("token")
return isLogin?children:<Redirect to="/login"></Redirect>
}
// 路由懒加载
const LazyLoad = (path) => {
const Comp = React.lazy(()=>import(`../views/${path}`))
return (
<React.Suspense fallback={ <>加载中...</> }>
<Comp/>
</React.Suspense>
)
}
最后 React Router V7 已经发布了,详细参考官方文档,记录学习博客:
原创作者: hubert-style 转载于: https://www.cnblogs.com/hubert-style/p/18950083