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 object
和Jump mode ('POP'/'PUSH'/'REPLACE')
- call
listen
The 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:
-
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
-
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