React-Router source code analysis

React-Router source code analysis

Simple use of React-Router

Official document

Handwritten mini React-Router

Link

React-Router
versus
Vue-Router
middle
Link
It is essentially a tag.

import {Component} from "react" ; export default class Link extends Component { render () { Const {to, Children, ...} = restProps the this .props; return < A the href = {} to { ... restProps }> {} Children </A > ; } } Copy code

BrowserRouter

according to

React-Router
In the library,
Router
Yes
react-router-dom
with
react-router-native
The public components, followed by the library file directory:
BrowserRouter
Quote
Router
And pass in a
history
parameter

import {Component} from "react" ; import {createBrowserHistory} from "history" ; import Router from "./Router" ; export default class BrowserRouter extends Component { constructor ( props ) { super (props); this .history = createBrowserHistory(); } render () { const {children} = this .props; return ( < Router history = {this.history} > {children} </Router > ); } } Copy code

Context

Create a global context to facilitate the recording of data and transfer across levels

Import React from 'REACT' ; const RouterContext = React.createContext(); export default RouterContext; copy the code

Router

Router
The sub-components need to be based on
BrowserRouter
Incoming
history
The parameters in are automatically rendered, in
Router
Need to monitor
location
The change. And pass in
Provider
The parameter needs to be an object, and the child component will be re-rendered according to its context

import React, {Component} from "react" ; import RouterContext from './Context' ; export default class Router extends Component { static computeRootMatch ( pathname ) { return { path : "/" , url : "/" , params : {}, isExact : pathname === "/" }; } constructor ( props ) { super (props); this .state = { location : props.history.location } //Monitor location changes this .unListen = props.history.listen( location => { this .setState({location}); }); } componentWillUnmount () { if ( this .unListen) { this .unListen(); } } render () { Const {Children, History} = the this .props; return < RouterContext.Provider value = {{ History , LOCATION: this.state.location , match: Router.computeRootMatch ( this.state.location.pathname ) }}> {children} </RouterContext.Provider > ; } } Copy code

Route

import React, {Component} from "react" ; import matchPath from "./matchPath" ; import RouterContext from "./Context" ; //Show the corresponding components, where children> component> render export default class Route extends Component { render () { return ( < RouterContext.Consumer > {(context) => { const {location} = context; //computedMatch is passed in by the Switch component, if it exists, it can be used directly to make a single selection const {path, children, component, render, computedMatch} = this.props; //Judge the current interface parameters according to location const match = computedMatch ? computedMatch : path ? matchPath(location.pathname, this.props) : context.match; const props = { ...context, match, }; return ( //First judge whether it matches, if it matches, judge whether there are children, then judge whether there is a component, and finally judge whether there is a render //If there is no match: judge whether there are children (404), if they exist, use children, if they do not exist, return null < div > {/* Reason for adding Provider: The component will read the current context value from the Provider that matches the component tree closest to itself, and the most recent props need to be used in the hook */} < RouterContext.Provider value = {props} > {match ? children ? typeof children === "function" ? children(props) : children : component ? React.createElement(component, props) : render ? render(props) : null : children ? typeof children === "function" ? children(props) : children : null} </RouterContext.Provider > </div > ); }} </RouterContext.Consumer > ); } } Copy code

Switch

import React, {Component} from "react" ; import RouterContext from './Context' ; import matchPath from "./matchPath" ; export default class Switch extends Component { render () { const {children} = this .props; return ( < RouterContext.Consumer > { context => { let match, element; const {location} = context; //Only take one match component for rendering React.Children.forEach(children, (child) => { if (match == null && React.isValidElement(child)) { element = child; const {path} = child.props; match = path? matchPath(location.pathname, path): context.match; } }); return match? React.cloneElement(element, {computedMatch: match }): null; } } </RouterContext.Consumer > ); } } Copy code

Redirect

Import React, the Component {} from 'REACT' ; Import RouterContext from './Context' ; export default class Redirect extends Component { render () { return ( < RouterContext.Consumer > {context => { const {history, push = false} = context; const {to} = this.props; return ( < LifeCycle onMount = {() => { push? history.push(to): history.replace(to); }} /> ); }} </RouterContext.Consumer > ); } } //Create an empty component and execute the function in it to jump class LifeCycle extends Component () { componentDidMount () { if ( this .onMount) { this .onMount(); } } render () { return null ; } } Copy code

withRouter

withRouter
It is essentially a high-level component that receives a component and will
RouteContext
middle
context
Context as
props
Pass in the receiving component together and return to the current receiving component

Import React from 'REACT' ; Import RouterContext from './Context' ; const withRouter = Component => props => { return < RouterContext.Consumer > {context => { //Pass the context as a parameter to Component return <Component {...props} {...context}/>; }} </RouterContext.Consumer> } export default withRouter;

hooks

import { useContext } from 'react'; import RouterContext from './Context'; export function useHistory() { return useContext(RouterContext).history; } export function useRouteMatch() { return useContext(RouterContext).match; } export function useLocation () { return useContext(RouterContext).location; } export function useParams ( params ) { //RouterContext takes the nearest Provider and needs to wrap a layer of RouterContext.Provider in Route const match = useContext(RouterContext).match; return match? match.params: null } Copy code

matchPath

Use directly

React-Router
Matching route file in

import pathToRegexp from "path-to-regexp" ; const cache = (); const cacheLimit = 10000 ; let cacheCount = 0 ; function compilePath(path, options) { const cacheKey = `${options.end}${options.strict}${options.sensitive}`; const pathCache = cache[cacheKey] || (cache[cacheKey] = {}); if (pathCache[path]) return pathCache[path]; const keys = []; const regexp = pathToRegexp(path, keys, options); const result = { regexp, keys }; if (cacheCount < cacheLimit) { pathCache[path] = result; cacheCount++; } return result; } /** * Public API for matching a URL pathname to a path. */ function matchPath(pathname, options = {}) { if (typeof options === "string" || Array.isArray(options)) { options = { path: options }; } const { path, exact = false, strict = false, sensitive = false } = options; const paths = [].concat(path); return paths.reduce((matched, path) => { if (!path && path !== "") return null; if (matched) return matched; const { regexp, keys } = compilePath(path, { end: exact, strict, sensitive }); const match = regexp.exec(pathname); if (!match) return null; const [url, ...values] = match; const isExact = pathname === url; if (exact && !isExact) return null; return { path, //the path used to match url: path === "/" && url === "" ? "/" : url, //the matched portion of the URL isExact, //whether or not we matched exactly params: keys.reduce((memo, key, index) => { memo[key.name] = values[index]; return memo; }, {}) }; }, null); } export default matchPath;