Real router V4 & V5 interceptor (hook), static route, route view implementation

Keywords: Front-end React Vue github Attribute

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:

Reference articles

Posted by sandrine2411 on Sat, 04 Apr 2020 13:35:56 -0700