preface

The purpose of this article is to deepen my understanding of the React-Router. However, after reading the source code, I found that the source code is not quite the same as some current articles, which may be due to the different versions. Therefore, I have analyzed the latest React-Router. Please show your mercy and give me more advice.

createBrowserHistory

This method exists in the third-party library History, which is a functional enhancement to window.history.

export function createBrowserHistory (
  options: BroswerHistoryOptions = {}
) :BrowserHistory {
  let { window = document.defaultView! } = options;
  
  // Used for event listening
  let listeners = createEvents<Listener>();
  
  // You can see that the createBrowserHistory implementation uses window.history
  let globalHistory = window.history;
  
  function applyTx(nextAction: Action) {
    action = nextAction;
    [index, location] = getIndexAndLocation();
    
    // When location changes, setState is applied to location within the Router source code
    listeners.call({ action, location });
  }
  
  function push (.) {
    // Some code is omitted.try {
      globalHistory.pushState(historyState, ' ', url);
    } catch (error) {
      window.location.assign(url);
    }
    applyTx(nextAction);
  }
  
  function replace (.) {
    // Some code is omitted. globalHistory.replaceState(historyState,' ', url);
    applyTx(nextAction);
  }
  
  let history: BrowserHistory = {
    ...
    // Add listen to window.history
    // Listen for window.location changes
    // When window.location changes, the corresponding callback function is executed
    listen(listener) {
      returnlisteners.push(listener); },... };return history;
}
Copy the code

As you can see, BrowserHistory’s push and replace actually use HTML5’s pushState and replaceState apis. Neither of these apis causes a page refresh, but changes the route. So this also makes it possible to have a single page application.

Of course, the core is the applyTx method. When we use push or replace, applyTx is called. This method is used to call the callback function that listens to location (see the Router source below).

createHashHistory

PushState and replaceState are essentially pushState and replaceState for route changes, but there are some additional actions to monitor and change routes.

export function createHashHistory(
  options: HashHistoryOptions = {}
) :HashHistory {
   Unlike createBrowserHistory, createHashHistory also listens for the Hashchange event
   window.addEventListener('hashchange'.() = >{... });function createHref(to: To) {
    // Note that hashHistory is just an extra '#' when creating a route
    return getBaseHref() + The '#' + (typeof to === 'string'? to : createPath(to)); }}Copy the code

react-router

When we write a route, we introduce BrowserRouter or HashRouter. Let’s look at how the two routers are implemented.

BrowserRouter source

import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";

class BrowserRouter extends React.Component {
  // You can see that there is a third party library called history to help you create history
  // createBroswerHistory is createBroswerHistory because it is BrowserRouter
  history = createHistory(this.props);

  render() {
    // Pass history and the corresponding children (Route) to the Router
    return <Router history={this.history} children={this.props.children} />; }}// Some code is omitted here.export default BrowserRouter;
Copy the code

HashRouter source

In fact, BrowserRouter is basically the same, interested can go to see the source code

Router source code implementation

class Router extends React.Component { constructor(props) { super(props); // This. State = {location: // this. State = {localtion: props.history.location }; this._isMounted = false; this._pendingLocation = null; <Redirect /> <Redirect /> If (! Props. StaticContext) {// Subscribe to location, SetState this.unlisten = props.history. Listen (location => {if (this._ismounted) {setState this.unlisten = props.history. this.setState({ location }); } else { this._pendingLocation = location; }}); } } componentDidMount() { this._isMounted = true; If (this._pendingLocation) {this.setState({location: this._pendingLocation }); }} // omit some code... < RouterContext.provider value={{history: this.props. History, location: this.state.location, match: Router.computeRootMatch(this.state.location.pathname), staticContext: This. Props. StaticContext}} > < HistoryContext. / / use the context save data Provider children = {this. Props. Children | | null} value={this.props.history} /> </RouterContext.Provider> ); }}Copy the code

Route source code implementation

Finished the Router, then there is the Route, we are in the process of writing routing, will put the < the Route > as < the Router > child of the component, in fact, of course, if you don’t do this will be an error

Basic
use

<Router>
  <Route path={path} component={component} />
</Router>
Copy the code

Look at the source implementation of Route

class Route extends React.Component {
  render () {
    // RouterContext.consumer is used
    // The Route can retrieve the data passed by the Router via the RouterContext.Provider<RouterContext.Consumer> {context => { ... / / if we are in the use of < the Route > didn't pass the location parameters, it can use the context, the location const location = this. Props. The location | | context. The location; const match = this.props.computedMatch ? this.props.computedMatch : MatchPath (location. Pathname, this.props) : // matchPath(location. Pathname, this.props) is used to compare the path attribute on the <Route> component. context.match; . / / the match is an object | null type of data / / if the match is object, will get back to the following format / / {path, url, isExact, Path return (<RouterContext.Provider value={props}> // For matched routes {props.match ? children ? typeof children === "function" ? __DEV__ ? evalChildrenDev(children, props, this.props.path) : children(props) : {Route Component ={component} /> {Route component={component} /> {Route component={component} /> {Route component={component} /> Component // omitted some code... </RouterContext.Provider> ); }} </RouterContext.Consumer> } }Copy the code

conclusion

  1. react-routerIt’s based on third-party librarieshistoryImplementation of the
  2. historyTo the nativewindow.historyEnhanced to make the enhancedhistoryHave tolocationMonitoring capability of
  3. BrowserRouterAs well asHashRouterHelped us create the correspondinghistoryAnd the incomingRouter
  4. RouterlocationI’ve been listening in and willlocationStored in thestate, in thelocationWhen changes occursetState
  5. <Route>locationIf the value is changed, the value is configured based on the value itselfpathAttribute tolocationRender the successfully matched component.

In fact, I think react-Router is essentially a publish-subscribe application scenario. The third party library History is used to publish location changes, while the React-Router subscribes to location changes to re-render components during location changes.