Navigation guard

As we know, Vue provides several hook functions that enable us to perform navigation guard functions. BeforeEach and afterEach are the global hook functions, beforeRouteEnter is the internal hook function. BeforeRouteUpdate and beforeRouteLeave, so we can do things like authenticate permissions or change TAB titles before or after a route jump

The React Router only provides a few components for routing and displaying content, but it does not provide any hook functions. Therefore, we need to implement this solution by ourselves

We need to encapsulate a routing guard component to get the history object via withRouter or useHistory

The history object has a listen function: it adds a listener to listen for changes in the address, and when the address changes, it calls the function passed to it

  • This function runs at a point in time:Before jumping to a new page
  • The passed function takes two arguments:Location objectJump mode ('POP'/'PUSH'/'REPLACE')
  • calllistenThe return value of this function is a function that unlistens for route changes

Jump mode (action) :

  • ‘POP’ : out of the stack; Go ()/history.goback ()/history.goforward () by clicking on the browser to go forward/back.

  • ‘PUSH’ : PUSH the stack; (Call history.push() to jump)

  • ‘REPLACE’ : push; (Call history.replace() to jump)

import {PureComponent} from 'react'
import {withRouter} from 'react-router-dom'

class GuardRouter extends PureComponent {
    componentDidMount() {
        this.unListen = this.props.history.listen((location, action) = > {
            console.log(location) / / location object
            console.log(action) // 'POP' / 'PUSH' / 'REPLACE'
            const prevLocation = this.props.location
            // The convention attribute onRouterChange passes a function to do something when the page jumps
            this.props.onRouterChange &&
                this.props.onRouterChange(prevLocation, curlocation, action, this.unListen)
        })
    }
    componentWillUnmount() {
        this.unListen() // Cancel listening for route changes
    }
    render() {
        return this.props.children
    }
}
export default withRouter(GuardRouter)
Copy the code
// Example:
import React from 'react'
import {BrowserRouter as Router} from 'react-router-dom'
import GuardRouter from './components/GuardRouter'
import Home from './views/Home'
import News from './views/News'
import AboutUs from './views/AboutUs'

let count = 0
function routerChange(prevLocation, curLocation, action, unListen) {
    count++
    console.log(
        ` log${count}:${prevLocation.pathname}${curLocation.pathname}.${action}Way `
    )
    if (count === 5) unListen()
}

export default function App() {
    return (<Router>
        <Link to='/'>Home page</Link>
        <Link to='/news'>The news page</Link>
        <Link to='/aboutus'>About us</Link>
        <GuardRouter onRouterChange={routerChange}>
            <Route path='/' component={Home} />
            <Route path='/news' component={News} />
            <Route path='/aboutus' component={AboutUs} />
        </GuardRouter>
    </Router>)}Copy the code

At this point, we have been able to listen to the change of the route jump, and we can get the information about the route jump, but it is still far from Vue’s route guard, because we still can’t block the route jump……

So, we need to set a block. Only if this.props.history. Block is set to block. The Router component’s getUserConfirmation property passes a blocking message as the first argument to the getUserConfirmation function. The second argument is a callback function. Callback (true)/callback(false))

The history. Block function takes one argument, either a string or a function that returns a string (it is run on each jump). The function that returns a string can take two arguments, just like the listen function (which takes arguments: The location and the action)

// GuardRouter
class GuardRouter extends PureComponent {
    componentDidMount() {
        this.unListen = this.props.history.listen((location, action) = > {
            // add listen...
        }
        // Set blocking
        this.props.history.block('Set blocking message: Sure jump page? ')}componentWillUnmount() {
        this.unListen()
    }
    render() {
        return this.props.children
    }
}
export default withRouter(GuardRouter)

// App
export default function App() {
    // Switch route trigger
    const getConfirm = useCallback((msg, callback) = > {
        console.log(msg) // Print: 'Set blocking message: Sure jump page? '
        Callback (true) // Jump
        // callback(false) // No jump
    }, [])

    return (<Router getUserConfirmation={getConfirm}>{/* Link and Route configuration */}</Router>)}Copy the code

Interceptor level increased

From the example above, we can see that getUserConfirmation is separate from block, so we need to wrap the GuardRouter up to replace the Router root component:

import {BrowserRouter as Router} from 'react-router-dom'
/* Auxiliary component, do not do display, only used to set blocking */
class _GuardRouterHelper extends PureComponent {
    componentDidMount() {
        // Set blocking
        this.props.history.block((location, action) = > {
            // Intercepting messages can be obtained dynamically
            // Get the location object to jump to and jump mode
            return ' '})}render() {
        return null}}const GuardRouterHelper = withRouter(_GuardRouterHelper)

// This component is not in the context of the Router and cannot block if the history object is not available
class GuardRouter extends PureComponent {
    handleRouterConfirm = (msg, next) = > {
        // Make the handler onBeforeEach before the route jump, and give the jump to the outer caller
        this.props.onBeforeEach &&
            this.props.onBeforeEach(next)
    }
    render() {
        return <Router getUserConfirmation={this.handleRouterConfirm}>
            <GuardRouterHelper />
            {this.props.children}
        </Router>}}export default RouterGuard // Direct export
Copy the code

Use the sample

import React from 'react'
import {BrowserRouter as Router} from 'react-router-dom'
import GuardRouter from './components/GuardRouter'
import Home from './views/Home'
import News from './views/News'
import AboutUs from './views/AboutUs'

function beforeRouterChange(next) {
    if (/* Satisfies the jump condition */) {
        next(true) // Forward route
    } else {
        next(false) // Do not forward routes}}export default function App() {
    return (<GuardRouter onBeforeEach={beforeRouterChange}>
        <Link to='/'>Home page</Link>
        <Link to='/news'>The news page</Link>
        <Link to='/aboutus'>About us</Link>
        <div>
            <Route path='/' component={Home} />
            <Route path='/news' component={News} />
            <Route path='/aboutus' component={AboutUs} />
        </div>
    </GuardRouter>)}Copy the code

Another promotion

By setting the block, we can pass a function that gets the location object and the action, so we can pass this information to the onBeforeEach function (the location object before the jump, The location object to be called to the path, jump mode and jump control method), then there are two options:

  1. PrevLocation, location, Action, and unBlock are all global variables (since the history object in the route is a common object and there is only one set of blocks) :

    import {BrowserRouter as Router} from 'react-router-dom'
    
    let prevLocation, curLocation, curAction, unBlock
    
    class _GuardRouterHelper extends PureComponent {
        componentDidMount() {
            / / set the blocked = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = >
            unBlock = this.props.history.block((location, action) = > {
                prevLocation = this.props.location
                curLocation = location
                curAction = action
                return ' '
            })
            / / set the blocked = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = >
        }
        render() {
            return null}}const GuardRouterHelper = withRouter(_GuardRouterHelper)
    
    class GuardRouter extends PureComponent {
        / * transfer global variables in turn out = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = > * /
        handleRouterConfirm = (msg, next) = > {
            this.props.onBeforeEach &&
                this.props.onBeforeEach(prevLocation, curLocation, curAction, next)
        }
        / * transfer global variables in turn out = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = > * /
        render() {
            return <Router getUserConfirmation={this.handleRouterConfirm}>
                <GuardRouterHelper />
                {this.props.children}
            </Router>}}export default GuardRouter // Direct export
    Copy the code
  2. If these variables have no special variables, they can be formatted as a JSON string returned by the json. stringify function and received by listening to the MSG variable of the function:

    import {BrowserRouter as Router} from 'react-router-dom'
    
    class _GuardRouterHelper extends PureComponent {
        componentDidMount() {
            / / set the blocked = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = >
            this.props.history.block((location, action) = > {
                const prevLocation = this.props.location
                // The return value is used as the first argument to listen
                return JSON.stringify({
                    prevLocation,
                    location,
                    action
                })
            })
            / / set the blocked = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = >
        }
        render() {
            return null}}const GuardRouterHelper = withRouter(_GuardRouterHelper)
    
    class GuardRouter extends PureComponent {
        / * using JSON. Parse parsing JSON string = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = > * /
        handleRouterConfirm = (msg, next) = > {
            const {
                prevLocation,
                location,
                action
            } = JSON.parse(msg)
            this.props.onBeforeEach &&
                this.props.onBeforeEach(prevLocation, location, action, next)
        }
        / * using JSON. Parse parsing JSON string = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = > * /
        render() {
            return <Router getUserConfirmation={this.handleRouterConfirm}>
                <GuardRouterHelper />
                {this.props.children}
            </Router>}}export default GuardRouter // Direct export
    Copy the code

Unblocker

As with adding route listeners, history.block returns an unblocking function. If you pass this unblocking function to onBeforeEach, you can only store it as a global variable and pass it as an argument

class _GuardRouterHelper extends PureComponent {
    componentDidMount() {
        this.unBlock = this.props.history.block((location, action) = > {
           // Add block})}componentWillUnmount() {
        this.unBlock() // Unblock
    }
    render() {
        return null}}const GuardRouterHelper = withRouter(_GuardRouterHelper)
Copy the code

The final code

The previous listener works with the blocker to intercept the route:

import {BrowserRouter as Router} from 'react-router-dom'
let unBlock
class _GuardRouterHelper extends PureComponent {
    componentDidMount() {
        unBlock = this.props.history.block((location, action) = > {
            const prevLocation = this.props.location
            return JSON.stringify({
                prevLocation,
                location,
                action
            })
        })
        this.unListen = this.props.history.listen((location, action) = > {
            const prevLocation = this.props.location
            // The convention attribute onRouterChange passes a function to do something when the page jumps
            this.props.onRouterChange &&
                this.props.onRouterChange(prevLocation, curlocation, action, this.unListen)
        })
    }
    componentWillUnmount() {
        ubBlock() // Unblock
        this.unListen() // Cancel listening for route changes
    }
    render() {
        return null}}const GuardRouterHelper = withRouter(_GuardRouterHelper)

class GuardRouter extends PureComponent {
    handleRouterConfirm = (msg, next) = > {
        const {
            prevLocation,
            location,
            action
        } = JSON.parse(msg)
        if (this.props.onBeforeEach) {
            this.props.onBeforeEach(prevLocation, location, action, next, unBlock)
        } else {
            next(true) // Default jump}}render() {
        return <Router getUserConfirmation={this.handleRouterConfirm}>
            <GuardRouterHelper onRouterChange={this.props.onRouterChange} />
            {this.props.children}
        </Router>}}export default GuardRouter

/ / use
// Determine whether to redirect the route
function beforeRouterChange(prevLocation, curLocation, action, next, unBlock) {
    if (/* Satisfies the jump condition */) {
        next(true) // Forward route
    } else {
        next(false) // Do not forward routes
    }
    // unBlock() // unBlock
}

// Route changes run the function
function handleRouterChange(prevLocation, curlocation, action, unListen) {
    // unListen() // Cancel route redirect listening
}

export default function App() {
    return (
        <GuardRouter
            onBeforeEach={beforeRouterChange}
            onRouterChange={handleRouterChange}
        >
            <Link to='/'>Home page</Link>
            <Link to='/news'>The news page</Link>
            <Link to='/aboutus'>About us</Link>
            <div>
                <Route path='/' component={Home} />
                <Route path='/news' component={News} />
                <Route path='/aboutus' component={AboutUs} />
            </div>
        </GuardRouter>)}Copy the code