premise
Before the v3 version of React Router, there was the onEnter hook function, which also supports the static routing configuration; however, after the v4 version, the hook function was removed, which is officially intended to be provided to developers, who can play it freely. In this case, we can only realize it ourselves. At present, there are many versions on the Internet, most of them are almost the same. Here is a summary and deepening. At the same time, it provides hook function or route guard in vue and static route configuration.
Implementation of hook function
The implementation of the hook function is relatively simple. You only need to wrap the official route. Here we allow the hook to support Promise asynchrony, and you need an asynchronous component. The code is as follows
Asynchronous component code
class AsyncBeforeEnter extends React.PureComponent { constructor(props) { super(props) this.state = { // Note: component and render will not be used at the same time here. The priority of render method is higher than that of component and render in Route // The target component is the same as < route component > Component: null, // The render method of the target component is the same as that of < route render > render: null, // error message error: null, // Mark whether asynchrony is complete completed: false } } componentDidMount() { const { beforeEnter, ...props } = this.props // beforeEnter hook function const enter = beforeEnter({ ...props }) if (isPromise(enter)) { // Determine if it is Promise enter .then(next => { this.handleAfterEnter(next) }) .catch(error => { console.error(error) this.setState({ error }) }) } else { this.handleAfterEnter(enter) } } handleAfterEnter(next) { // Result processing const { route = {}, ...props } = this.props // If the result is null or undefined or true: render the component directly without any processing if (next === null || next === undefined || next === true) { this.completed(route.component, route.render) return } // Return false: prevents rendering of components if (next === false) { this.completed(null) return } // Return string: jump route, similar to 302 status code in http // Here, use the Redirect of React Router to jump if (typeof next === 'string') { this.completed(null, () => <Redirect to={next} from={props.location.pathname} />) return } // Return to React component if (typeof next === 'function' || React.isValidElement(next)) { this.completed(null, () => next) return } // Return Object: if there is an attribute of redirect=true, jump // Otherwise, use the Route component to render if (isPlainObject(next)) { const { redirect, ...nextProps } = next if (redirect === true) { this.completed(null, () => <Redirect {...nextProps} {...{ from: props.location.pathname }} />) return } this.completed(() => <Route {...nextProps} />) return } warn(`"${props.location.pathname} => beforeEnter" hook return values error. expected null? undefined? true? React.Component? HTMLElement? Route props? route props detail to see https://reacttraining.com/react-router/web/api/Route https://reacttraining.com/react-router/web/api/Redirect` ) // Exceptions prevent rendering of components this.completed(null) } /** * Change the state rendering component when finished: * @param component * @param render */ completed(component, render) { this.setState({ Component: component, render, completed: true, error: null }) } getExtraProps() { // Remove the hook function and get other props const { loading: Loading, beforeEnter, ...props } = this.props return { ...props } } render() { const { Component, render, error, completed } = this.state if (!completed) { // Hang in the air return null } // Completed if (render && typeof render === 'function') { return render(this.getExtraProps()) } return Component ? <Component {...this.getExtraProps()} /> : null } }
Route with hook function
Name it privaterooute
export default (route) => ( <Route path={route.path} exact={route.exact} strict={route.strict} location={route.location} sensitive={route.sensitive} children={route.children} render={props => { // beforeEnter const { beforeEnter, ...nextProps } = route // If there are hook functions, execute with asynchronous components if (route.beforeEnter && typeof route.beforeEnter === 'function') { return ( <AsyncBeforeEnter beforeEnter={beforeEnter} route={nextProps} {...props} {...extraProps} /> ) } // Direct Render return ( route.render && typeof route.render ? ( route.render({ ...props, ...extraProps, route: nextProps }) ) : ( route.component ? ( <route.component route={nextProps} {...props} {...extraProps} /> ) : null ) ) }} /> )
The Route can be used instead of the official
Example:
<PrivateRoute path="/" component={Example} beforeEnter={(props) => check(props) }/> <PrivateRoute path="/user" component={User} beforeEnter={(props) => check(props) }/>
Static route configuration and hook function support
The static route configuration official page gives the scheme, as shown in: react-router-config The static routing configuration of this paper also refers to this implementation, rewrites its implementation and adds the hook function
Static routing configuration
The basic static routing table is as follows
// Top two routes // A login // Others need to be returned after authorization export default [ { path: '/example', key: 'example', component: Example, beforeEnter(props) { if (auth(props.localtion.pathname)) { return true } return '/login' }, // Subrouter routes: [ { path: '/example1', key: 'example1', component: Example1, } ] }, { path: '/login', key: 'login', component: Login } ]
Rewrite hook function to render static route table = > renderroutes
// renderRoutes export default (routes, switchProps = {}, extraProps = {}) => { return routes && routes.length > 0 ? ( <Switch {...switchProps}> { routes.map((route, i) => ( <Route key={route.key || i} path={route.path} exact={route.exact} strict={route.strict} location={route.location} sensitive={route.sensitive} children={route.children} render={props => { // beforeEnter const { beforeEnter, ...nextProps } = route if (route.beforeEnter && typeof route.beforeEnter === 'function') { return ( <AsyncBeforeEnter beforeEnter={beforeEnter} route={nextProps} {...props} {...extraProps} /> ) } return ( route.render && typeof route.render ? ( route.render({ ...props, ...extraProps, route: nextProps }) ) : ( route.component ? ( <route.component route={nextProps} {...props} {...extraProps} /> ) : null ) ) }} /> )) } </Switch> ) : null }
The renderRoutes method can be called with. This example is taken from the official example:
const Root = ({ route }) => ( <div> <h1>Root</h1> {/* child routes won't render without this */} {renderRoutes(route.routes)} </div> ); const Home = ({ route }) => ( <div> <h2>Home</h2> </div> ); const Child = ({ route }) => ( <div> <h2>Child</h2> {/* child routes won't render without this */} {renderRoutes(route.routes, { someProp: "these extra props are optional" })} </div> ); const GrandChild = ({ someProp }) => ( <div> <h3>Grand Child</h3> <div>{someProp}</div> </div> ); ReactDOM.render( <BrowserRouter> {/* kick it all off with the root route */} {renderRoutes(routes)} </BrowserRouter>, document.getElementById("root") );
Implement the route view function similar to Vue router
After the above processing, the basic hook function and static route are configured. Although the function is completed, it always feels a bit troublesome to use. Really, is there a one-step like route view in Vue router? OK, arrange...
It's not recommended to use React context before v16, but now it's mature and can be used boldly; if you don't know how and what principle to use it, you can To this place, please Fill in the knowledge
Here's another key point. Look at the picture and draw the key points:
It can be reused. The internal value will cover the external value, so we can nest multiple routes;
Create context
import React from 'react' const RouteContext = React.createContext([]) // Used in devtool RouteContext.displayName = 'RouteViewContext' export const RouteProvider = RouteContext.Provider export const RouteConsumer = RouteContext.Consumer
Create RouteView
import { RouteConsumer } from './context' import renderRoutes from './renderRoutes' //RouteView export default () => { return ( <RouteConsumer> {/* Rendering with static routes */} {/* ruotes Provided by RouteProvider */} {routes => renderRoutes(routes)} </RouteConsumer> ) }
Rewrite renderRoutes again so that it can render subordinate routes
import { RouteProvider } from './context' // renderRoutes export default (routes, switchProps = {}, extraProps = {}) => { return routes && routes.length > 0 ? ( <Switch {...switchProps}> { routes.map((route, i) => ( <Route key={route.key || i} path={route.path} exact={route.exact} strict={route.strict} location={route.location} sensitive={route.sensitive} children={route.children} render={props => { checkProps(props) // beforeEnter const { beforeEnter, ...nextProps } = route // RouteProvider provides the data required by subordinate routes if (route.beforeEnter && typeof route.beforeEnter === 'function') { return ( <RouteProvider value={route.routes}> <AsyncBeforeEnter beforeEnter={beforeEnter} route={nextProps} {...props} {...extraProps} /> </RouteProvider> ) } return ( <RouteProvider value={route.routes}> { route.render && typeof route.render ? ( route.render({ ...props, ...extraProps, route: nextProps }) ) : ( route.component ? ( <route.component route={nextProps} {...props} {...extraProps} /> ) : null ) } </RouteProvider> ) }} /> )) } </Switch> ) : null }
Use
Add the following code to the entry js
import { RouteProvider, RouteView } from '../router' // Static routing const routes = [ // A little. ] class App extends React.PureComponent { // A little. render() { return ( // A little. // Provide top-level routing // Subordinate route renderRoutes processing <RouteProvider value={routes}> <RouteView /> </RouteProvider> // A little. ) } } export default App
Use of level two routing
class Example extends React.PureComponent { // A little. render() { // There is no need to provide routes here // renderRoutes has been provided by RouteProvider return ( <div> Example <RouteView /> </div> ) } } export default Example
Through the above efforts, we have static routing, hook function, and router view similar to Vue router;
The result of the final effort:
- Convenient authentication
- Easy routing management
- Multi level routing one component can realize rendering Source: Real router V4 & V5 interceptor (hook), static route, route view implementation
Reference articles
- https://reacttraining.com/react-router/web/guides/quick-start
- https://github.com/ReactTraining/react-router/tree/master/packages/react-router-config
- https://github.com/mirrorjs/mirror/issues/78
- https://stackoverflow.com/questions/50928349/react-router-4-how-to-wait-for-a-promise-to-resolve-inside-render-method-of-ro
- https://juejin.im/post/5d2d32a9f265da1b8b2b90c7
- https://zh-hans.reactjs.org/docs/context.html