In the blog React Advanced (4) : Route Introduction, the knowledge about React routing is introduced. In the actual project development process, the jump between routes must involve the determination of permissions, user login and other restrictions, so the navigation guard is required to complete this matter.

Before implementing React – Router interception, let’s take a look at how vUE interception is used and what it does.

True to its name, vue-Router provides navigational guards that are primarily used to guard navigation by jumping or cancelling.

Global guard

You can register a global front-guard using router.beforeeach:

const router = new VueRouter({ ... })
 
router.beforeEach((to, from, next) => {
  // ...
Copy the code

When a navigation is triggered, the global front-guard is called in the order it was created. The guard resolves asynchronously, in which case the navigation waits until all the guards resolve.

As you can see, Vue can listen for all route jumps in beforeEach. If it matches the rule, it jumps, if not, it jumps to the specified location.

Why can’t react-Router provide the same API? The author replied on Github:

You can do this from within your render function. JSX doesn’t need an API for this because it’s more flexible.

It means:

You can do this in the render function. JSX does not need an API because it is more flexible.

In my reply, I hope the React router is flexible. I don’t want to add too many apis to the router. These apis should allow users to implement route interception according to their own needs. Let’s start implementing a route interception that meets most of our requirements.

React – Router version: 4.0

First, use the react-router-config to write a route configuration as an array:

//routerConfig.js
const routes = [
    {
        path: '/',
        component: 'component/app',
        routes: [
            {
                path: '/asd',
                component: 'component/topics',
                routes: [
                    {
                        path: '/asd/login',
                        component: 'component/home'
                    }
                ]
            }
        ]
    }
]
 
export default routes
Copy the code

Write in configuration mode, because this can be very intuitive to see the routing configuration of the whole project, know to jump to what position, under what position, what kind of components will be displayed.

As you can see, there are two areas that are different from the document:

  1. There’s only one list item in the entire array, only one object there.
  2. compoentIs a string, not an object or method.

First point: because the current page may need a root route, in the root route, may do some operations such as setting the theme color, global content display, here can do, the rest, in his routes to do ok.

Second point: The purpose of this is to achieve faster routing rendering, in normal use is generally like this:

// Pseudo code for reference only
import A from './a'
{
    path:'/',
    component:A
}
Copy the code

Basically, this implementation presents a problem: What if our page has 20, or even 50, or 100, paths to jump to? In this way, when loading the router.js file, so many files need to be imported, which will affect the efficiency of code execution and page rendering speed.

How to render, we will subdivide one by one in the following.

And since the whole route is an array, we’re going to do a loop here if we do it in the most cumbersome way possible.

<Route path='/' component={a} />
<Route path='/b' component={b} />
<Route path='/c' component={c} />
<Route path='/d' component={d} />
...
Copy the code

Obviously, this is not a wise way to implement a route jump, we want to write a maintainable, readable route.

So, here’s a method for traversing the route array.

//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} /> )} /> ) }) ) exportdefault renderRoutesMap
Copy the code

Here the route rendering to do a small traversal, the route object to do a traversal, the key to!

RouterGuard is our focus, and this is where we do real route interception!

//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)
constmapDispatchToProps = 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('Intercept before jump'.this.props)
    }
    render(a) {
        let { component, routes = [] } = this.props
        console.log('Before rendering 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))
Copy the code

componentWillMountisreactThe life cycle in the component, called before rendering, is available herereduxIn the parameters, you can also get the parameters brought through the route.

In this case, I use authorization. If my authorization is true, it means I am not logged in and jump to the login login page.

I also did some redirection, like in the root route, I wanted to redirect to another address.

In fact, this can achieve true route interception, but, this is not enough, we not only do interception, we also need to do, in addition to interception, to speed up the rendering as much as possible, improve the user experience.

Here, I use a react-loadable route lazy load component. Its function is used for route lazy load to load higher-order components with dynamically imported components to improve user experience.

The import used here does not support variables, so we use template string to import the component every time we enter the component and prepare render. In this way, we do not waste resources in each rendering and the speed of the first rendering can be faster.

Finally, the route configuration, route array rendering, and route component rendering are splicing together a whole React-Router route interception (navigation guard)

//renderRoutes.js
import renderRoutesMap from './renderRoutesMap'
/** * renderRoutes *@param{array} routes Route list *@param{object} extraProps = properties of {} extra *@param{object} switchProps = {} Switch properties */
constrenderRoutes = ({ routes, extraProps = {}, switchProps = {} }) => ( <Router> <Switch {... switchProps}> {renderRoutesMap(routes)} </Switch> </Router> ) exportdefault renderRoutes
 
//index.js
const router = () => (
    renderRoutes({
        routes: routerConfig
    })
)
export default router
Copy the code

Finally, add index.js to the page.