React-router-dom router-dom
React Router
- Router Component (BrowserRouter)
- Route Matching (Route, Switch, Redirect)
- Navigation Component (Link)
- Hooks methods (useRouteMatch, useHistory, useLocation, useParams)
- High order components (withRouter)
- Utility components (Prompt)
background
I believe many people like me will encounter many questions when learning react-Router, such as the three rendering modes of route component, render and children and their priority, Redirect principle and Switch exclusive route implementation. Today we reveal the secrets. Today these implementations are just a simple treatment, regardless of compatibility issues.
The router components
BrowserRouter
import React, {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() { return <Router history={this.history} children={this.props.children} />; }}Copy the code
BrowserRouter relies on a history library whose main purpose isto pass in the history parameter. Make the props in children include the history object. Can invoke some methods on the history, such as push, goBack to go, the replace, listen…
Add the history interface parameters to make sure you know what methods and parameters are not allowed on the history object. Here for generic constraints do not do too much explanation, interested can refer to the history of library making address https://github.com/ReactTraining/history
interface History { length: number; action: 'PUSH' | 'POP' | 'REPLACE'; location: Location<HistoryLocationState>; push(path: Path, state? : HistoryLocationState): void; push(location: LocationDescriptor<HistoryLocationState>): void; replace(path: Path, state? : HistoryLocationState): void; replace(location: LocationDescriptor<HistoryLocationState>): void; go(n: number): void; goBack(): void; goForward(): void; block(prompt? : boolean | string | TransitionPromptHook<HistoryLocationState>): UnregisterCallback; listen(listener: LocationListener<HistoryLocationState>): UnregisterCallback; createHref(location: LocationDescriptorObject<HistoryLocationState>): Href; }Copy the code
Next, let’s look at the Router component referenced in BrowserRouter. This is the core, and regardless of compatibility, we use react.createcontext () to build the RouterContext. For simplicity, I’m not going to build the HistoryContext. The Router component calls History’s Listen method to listen for routes, and puts the value of the location that it listens to into the RouterContext. Once the Location is updated, the child component rerenders. This is the main reason why you must pass the value through coonText. ComputeRootMatch returns a parameter object even if path does not exist. Equivalent to a default value.
import React, {Component} from "react"; const RouterContext = React.createContext() 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 }; // This.unlisten = props.history. Listen (location => {this.setState({location}); }); } componentWillUnmount() { if (this.unlisten) { this.unlisten(); } } render() { return ( <RouterContext.Provider value={{ history: this.props.history, location: This.state. location, // path url params isExact match: Router.computeRootMatch(this.state.location.pathname) }}> {this.props.children} </RouterContext.Provider> ); }}Copy the code
Implementation of functional components:
import React, { useState, useEffect } from "react"; import { RouterContext } from "./Context"; // This is equivalent to const RouterContext = react.createcontext (), but try to put the RouterContext in a directory so that other components can consume it. export default function Router(props) { const [location, setLocation] = useState(props? .history? .location); UseEffect (() => {// setLocation() // location changes, To perform the callback here const unlisten = props.history.listen((location) => {setLocation(location); }); return () => { if (unlisten) { unlisten(); }}; }, [props.history]); const computeRootMatch = (pathname) => { return { path: "/", url: "/", params: {}, isExact: pathname === "/" }; }; return ( <RouterContext.Provider value={{ history: props.history, location: Location, // path url params isExact computeRootMatch(location.pathname), }} > {props.children} </RouterContext.Provider> ); }Copy the code
The route matching components
Route
The Route component is the core component of the React-router-DOM. It contains the implementation of Route matching and the logic of Route rendering. In this section, you can understand the priority of the Route rendering. And what they did to make the route match and whether the children component matches both render. Now let’s break it down one by one. First of all, matchPath is mainly used to determine whether routes match. The function returns the following list of values:
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; }, {}};Copy the code
The match parameter is calculated to determine whether there is a computeMatch. The props parameter is used to implement exclusive routing. If there is a computeMatch, the computeMatch is passed from the Switch. It breaks the original matching rules in Route so that children are passed without rendering if they do not match. See the Switch implementation for details. The Router uses computeRootMatch (computeRootMatch, computeRootMatch, computeRootMatch, computeRootMatch, computeRootMatch, computeRootMatch, computeRootMatch, computeRootMatch).
const {children, component, render, path, computedMatch} = this.props; const match = computedMatch ? computedMatch : path ? matchPath(location.pathname, this.props) : context.match;Copy the code
The next core is the implementation of three render modes for the component, children> Component >render priority. Look at the source code and you’ll see why. If match matches children, then children(props) is the function. If match matches children, then children(props) is the function. Otherwise, render the children component. If the match doesn’t match, judge children, and here you’ll see that if there’s a route mismatch, if there’s a children component it’s going to be rendered.
<RouterContext.Provider value={props}>
{match
? children
? typeof children === "function"
? children(props)
: children
: component
? React.createElement(component, props)
: render
? render(props)
: null
: typeof children === "function"
? children(props)
: null}
</RouterContext.Provider>
Copy the code
Route core code
import React, {Component} from "react"; import matchPath from "./matchPath"; import {RouterContext} from "./Context"; export default class Route extends Component { render() { return ( <RouterContext.Consumer> {context => { const location = context.location; const {children, component, render, path, computedMatch} = this.props; const match = computedMatch ? computedMatch : path ? matchPath(location.pathname, this.props) : context.match; const props = { ... context, match }; // match children, component, render, null // match children (function), null return ( <RouterContext.Provider value={props}> {match ? children ? typeof children === "function" ? children(props) : children : component ? React.createElement(component, props) : render ? render(props) : null : typeof children === "function" ? children(props) : null} </RouterContext.Provider> ); }} </RouterContext.Consumer> ); }}Copy the code
Link
The internal implementation of the Link component is also very simple, mainly a tag, and then disallow the default jump behavior of a Link. The Link component has an important property, to, which represents the route to jump to when clicked. In addition, the context is used, and the main thing isto receive the history object. The code for class and functional components is as follows:
import React, {Component,useContext} from "react"; import {RouterContext} from "./Context"; export default class Link extends Component { static contextType = RouterContext; handleClick = event => { event.preventDefault(); / / events do jump. Context. History. Push (this, props to); }; render() { const {to, children, ... otherProps} = this.props; return ( <a href={to} {... otherProps} onClick={this.handleClick}> {children} </a> ); }}Copy the code
Functional component implementation
export default function Link(props) {
const context = useContext(RouterContext);
const{ to, children, ... restProps } = props;const handleClick = (event) = > {
event.preventDefault();
// the event does the jump
context.history.push(to);
};
return (
<a href={to} {. restProps} onClick={handleClick}>
{children}
</a>
);
}
Copy the code
Switch
The Switch is an exclusive route and matches only the first matching component during route matching. The core is to traverse the Children component of Switch to find a matching route. React.Children. ForEach (Children, function[(thisArg)]); And set this to thisArg. If children is an array, it is iterated over and called for each child node in the array. Note that if children is a Fragment object, it will be treated as a single child node and will not be traversed. React.cloneElement(element, {computedMatch: match}) computedMatch methods in the Route are passed to break the matching rules in the Route so that exclusive routes can be implemented. CloneElement (Element,[props],[…children]) clones the Element and returns the new React element. The props of the element is the result of superficially merging the new props with the props of the original element. The new child element replaces the existing child element, and the key and ref from the original element are retained. The React. IsValidElementType method is used to determine whether the target is a valid React element type. The following types are considered valid: String, function, and ReactSymbol
import React, {Component} from "react"; import {RouterContext} from "./Context"; import matchPath from "./matchPath"; Export Default Class Switch extends Component {render() {return (< RouterContext.consumer > {context => {//match matches // Element records the matched element const {location} = context; let match, element; React.Children.forEach(this.props.children, child => { if (match == null && React.isValidElement(child)) { element = child; const {path} = child.props; match = path ? matchPath(location.pathname, child.props) : context.match; }}); return match ? React.cloneElement(element, { computedMatch: match }) : null; }} </RouterContext.Consumer> ); }}Copy the code
Redirect
Redirect refers to route redirection, mainly to a specified route. Redirect contains the to attribute, which represents the specified route to be redirected. LifeCycle is a function that implements the route jump logic. The Render process of the class component cannot do logic during the specified LifeCycle, so it relies on this intermediate component.
import React, {Component} from "react"; import {RouterContext} from "./Context"; export default class Redirect extends Component { render() { return ( <RouterContext.Consumer> {context => { const {history} = context; const {to, push = false} = this.props; return ( <LifeCycle onMount={() => { push ? history.push(to) : history.replace(to); }} / >); }} </RouterContext.Consumer> ); } } class LifeCycle extends Component { componentDidMount() { if (this.props.onMount) { this.props.onMount.call(this, this); } } render() { return null; }}Copy the code
Hooks method
The hooks method is much simpler to implement.
import React, {useContext} from "react"; import {RouterContext} from "./Context"; Export function useHistory() {return useContext(RouterContext).history; } export function useLocation() {// Get location return useContext(RouterContext).location; } export function useRouteMatch() { return useContext(RouterContext).match; } export function useParams() { const match = useContext(RouterContext).match; return match ? match.params : {}; }Copy the code
Isn’t that easy? If the class component does not use hooks, then how do you get these parameter methods?
withRouter
WithRouter is used to get params, Location, History, routeMatch, and see if it’s easy to implement.
import React from "react"; import {RouterContext} from "./Context"; const withRouter = WrappedComponent => props => { return ( <RouterContext.Consumer> {context => { return <WrappedComponent {... props} {... context} />; }} </RouterContext.Consumer> ); }; export default withRouter;Copy the code
Practical component
When you want to leave the page, an alert will pop up, allowing you to choose whether to leave. The React route also has such a component for us, called prompt. The tag has two attributes: message: the text message used to display the prompt. When: Passes a Boolean value, equivalent to the label switch. Default is true and invalid when set to false.
import React from "react";
import { RouterContext } from "./Context";
export default function Prompt({ message, when = true }) {
return (
<RouterContext.Consumer>
{(context) => {
if (!when) {
return null;
}
let method = context.history.block;
return (
<LifeCycle
onMount={(self) => {
self.release = method(message);
}}
onUnMount={(self)=>{
self.release();
}}
></LifeCycle>
);
}}
</RouterContext.Consumer>
);
}
class LifeCycle extends React.Component {
componentDidMount() {
if (this.props.onMount) {
this.props.onMount.call(this, this);
}
}
componentWillUnmount() {
if (this.props.onUnMount) {
this.props.onUnMount.call(this, this);
}
}
render() {
return null;
}
}
Copy the code
At this point, it is time to end, of course, there is a NavLink component, but you know the Link implementation, this is also very well implemented, it is just a Link with some properties, there is no repeat here. The react-router-dom implementation is the core implementation of the react-router-dom, but it is a bit more rigorous, with some environment judgments and some warning and error handling. Git address https://github.com/wjb-code/react-router-dom.git Welcome to see the complete code.