如何實現一個react-router路由攔截(導航守衛)

熱情的劉大爺發表於2019-01-06

在我們自己實現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路由攔截官方說明

在作者的回覆當中可以看到,他希望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就可以了。

這是本人在學習和工作當中使用的方式,如果哪裡有什麼不對了,還希望大家可以給予指出。最後,希望大家多點贊,多關注,謝謝啦?

相關文章