在我們自己實現react-router的路由攔截之前,我們先看一下vue的路由攔截是怎麼使用的,都做到了哪些事情:
正如其名,vue-router 提供的導航守衛主要用來通過跳轉或取消的方式守衛導航。
全域性守衛
你可以使用 router.beforeEach 註冊一個全域性前置守衛:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
複製程式碼
當一個導航觸發時,全域性前置守衛按照建立順序呼叫。守衛是非同步解析執行,此時導航在所有守衛 resolve 完之前一直處於 等待中。
在這裡,我們可以看到,vue在所有的路由跳轉前,在beforeEach中可以監聽所有的路由跳轉,如果符合規則,就進行跳轉,如果不符合,那麼就跳轉到我指定的位置。
react-router當中,為什麼不能提供同樣的api呢?作者在github中回覆到:
You can do this from within your render function. JSX doesn't need an API for this because it's more flexible.
複製程式碼
大概的意思就是就是:
您可以在渲染功能中執行此操作。JSX不需要API,因為它更靈活。
複製程式碼
在作者的回覆當中可以看到,他希望react-router是靈活的,不希望在裡面新增太多的api,這些api應該是讓使用者,根據自己的需求去實現自己的路由攔截。下面,就開始實現一個自己的,可以滿足大部分需求的路由攔截。
react-router版本:4.0
首先,我們要使用react-router-config,用陣列的形式去寫一個路由的配置:
//routerConfig.js
const routes = [
{
path: '/',
component: 'component/app',
routes: [
{
path: '/asd',
component: 'component/topics',
routes: [
{
path: '/asd/login',
component: 'component/home'
}
]
}
]
}
]
export default routes
複製程式碼
用配置的方式寫,是因為這樣可以很直觀的看出來,我們整個專案的路由配置,知道我們具體要跳轉到什麼位置,在什麼位置下,會顯示什麼樣的元件。
應該可以看出來,這裡面我寫的兩個地方,和文件是有區別的:
1.我整個陣列只有一個列表項,只有這個一個物件。
2.我的compoent的值是字串,而不是一個物件或者方法
複製程式碼
第一點:因為,我們可能要在當前的頁面中,需要一個根路由,在根路由中,我們要可能做一些類似與主題顏色的設定,全域性內容的展示之類的操作,在這裡,我們就可以做到了,剩下的,都在他的routes裡面去做就ok了。
第二點:這麼做的目的,就是為了實現路由更快的渲染,在正常的使用方式中,我們一般都是這樣的:
//虛擬碼,僅供參考
import A from './a'
{
path:'/',
component:A
}
複製程式碼
基本上是這樣實現的,這樣實現是有一個問題,如果我們的頁面,有20哥,甚至50個、100個要跳轉的路徑,怎麼辦,這樣我們每次載入到router.js這個檔案的時候,是需要把這麼多的檔案都import過來,這樣是很影響程式碼的執行效率和頁面的渲染速度的。
具體怎麼渲染,我們會在下面去一一細分。
由於,路由的整體,是一個陣列(array),我們要在這裡,去做一個迴圈,如果我們用最笨重的方式去實現
<Route path='/' component={a} />
<Route path='/b' component={b} />
<Route path='/c' component={c} />
<Route path='/d' component={d} />
...
複製程式碼
很明顯,這個樣子去實現一個路由的跳轉,是不明智的,我們要寫的是一個可維護,易讀性強的路由。
所以,我在這裡寫了一個用來遍歷路由陣列的方法。
//renderRoutesMap.js
import RouterGuard from './routerGuard'
const renderRoutesMap = (routes) => (
routes.map((route, index) => {
return (
<Route key={index} path={route.path} render={props => (
<RouterGuard {...route} {...props} />
)}
/>
)
})
)
export default renderRoutesMap
複製程式碼
這裡我們把路由的渲染做了一個小小的遍歷,把我們的路由物件,去做了一次遍歷,重點來了!!!
RouterGuard就是我們的重點,在這裡,我們就要做真正的,路由攔截(導航守衛)!
//routerGuard.js
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
import Loadable from 'react-loadable'
import { connect } from 'react-redux'
import renderRoutesMap from './renderRoutesMap'
const mapStateToProps = state => (state)
const mapDispatchToProps = dispatch => ({ ...dispatch })
class RouterGuard extends Component {
constructor(props) {
super()
}
componentWillMount() {
let { history: { replace }, authorization, location } = this.props
if (authorization) replace('./login')
if (location.pathname === '/') replace('./asd')
console.log('路由跳轉前的攔截', this.props)
}
render() {
let { component, routes = [] } = this.props
console.log('準備渲染compoent前', this.props)
const LoadableComponent = Loadable({
loader: () => import(`../${component}`),
loading: () => (
<span>11111</span>
)
})
return (
<div>
<LoadableComponent {...this.props} />
{renderRoutesMap(routes)}
</div>
)
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(RouterGuard))
複製程式碼
在這裡,其實是我專案當中使用的程式碼,所以裡面會有react-redux,大家如果不使用redux的話,可以自行拆解。
componentWillMount是react元件當中的生命週期,在渲染前呼叫,在這裡,我們可以拿到redux當中的引數,也可以拿到通過路由帶過來的引數。
在這裡,我用的是authorization,如果我的authorization是true,就代表我沒有登入,跳轉到login登入頁。
我還做了重定向的操作,比如在根路由的時候,我要重定向跳轉到另外一個地址。
其實這樣就可以實現真正的路由攔截了,但是,這個還不夠,我們不只要做攔截,我們還要做的是,除攔截以外,還要儘可能的加快渲染速度,提升使用者體驗。
這裡,我有用到一個Loadable的外掛,他的作用就是用於載入具有動態匯入的元件的更高階元件,提升使用者體驗。
我在這裡,才用的import,improt是不支援變數的,所以我這裡用的是模版字串的方式,在每一次進入元件,並準備render的時候,才去import該元件,這樣,可以在每一次渲染的時候不用浪費資源,也可以保證首次渲染的速度變快。
最後,我們把路由配置、路由陣列渲染、路由元件渲染給拼接一下,一整個react-router路由攔截(導航守衛)
//renderRoutes.js
import renderRoutesMap from './renderRoutesMap'
/**
* renderRoutes 渲染路由
* @param {array} routes 路由列表
* @param {object} extraProps = {} extra的屬性
* @param {object} switchProps = {} switch的屬性
*/
const renderRoutes = ({ routes, extraProps = {}, switchProps = {} }) => (
<Router>
<Switch {...switchProps}>
{renderRoutesMap(routes)}
</Switch>
</Router>
)
export default renderRoutes
//index.js
const router = () => (
renderRoutes({
routes: routerConfig
})
)
export default router
複製程式碼
最後在頁面當中,引入index.js就可以了。
這是本人在學習和工作當中使用的方式,如果哪裡有什麼不對了,還希望大家可以給予指出。最後,希望大家多點贊,多關注,謝謝啦?