Let’s start with what hashHistory is.

As you know, the two routing strategies we use in single-page applications are hashHistory and browserHistory. The browserHistory is the old way of routing, from birth has been used to now, such as the Denver nuggets personal address https://juejin.cn/user/1574156384094397 pages. And hashHistory is a single page application (SPA), widely used in a way of routing, it USES the path behind the # as page identifier, such as https://i.ai.mi.com/h5/ai-xiaoai-3-years-fe/#/home. The former makes it more difficult to determine the project business path (XXX /static/yy-prj-name/home), while the latter can be easily determined by the path after #.

History Project Information:

  • Project address: github.com/ReactTraini…
  • ReactTraining/history ReactTraining
  • History is the core dependency of the React-Router
  • A rollup packaging

The project structure

The core code of the project is in modules, where the key files are createBrowserHistory.js, createHashHistory.js, createMemoryHistory.

createHashHistory

The createHashHistory.js file exports a createHashHistory function.

CreateHashHistory is used as follows:

// Default values are given below
createHashHistory({
  basename: ' '.// The base URL of the app (see below)
  hashType: 'slash'.// The hash type to use (see below)
  getUserConfirmation: (message, callback) = > callback(window.confirm(message))
});
Copy the code
  • basenameIs a string that is carried by default when a route changes
  • hashTypeI can ignore it and use the default
  • getUserConfirmationThis is for usehistory.blockTo handle the page exit function,history.block(blockInfo) blockInfoIt can be a function or a string. The result of the function execution is the message above, or if it is a string, the string is the message passed in.

CreateHashHistory (props) returns the following object, which is what we normally use when we use the react router:

  const history = {
    length: globalHistory.length,
    action: 'POP'.location: initialLocation,
    createHref,
    push,
    replace,
    go,
    goBack,
    goForward,
    block,
    listen,
  };
Copy the code

We analyze the derived items one by one.

history.push

This is a common one, and the simplified push code looks like this.

Parameters:

The path object or string, which is the address we want to push, is parsed as an object (locationObj) if it is a string. If it’s an object then the address after push is /user/game, right? Name =lxfriday#hahaha, which can contain the following three attributes.

history.push({
  pathname: '/user/game'.search: 'name=lxfriday'.hash: 'hahaha'
})
Copy the code

Such as the/user/game? Name =lxfriday#hahaha, if basename is not set, then the address after push is /user/game? Name =lxfriday#hahaha, if basename is set to /base, then the address after push is /base/user/game? Name = lxfriday# hahaha.

  function push(path, state) {
    const action = 'PUSH';
    const location = createLocation(
      path,
      undefined.undefined.// The location information before the address change
      history.location
    );
    transitionManager.confirmTransitionTo(
      location,
      action,
      getUserConfirmation,
      (ok) => {
        if(! ok)return;

        const path = createPath(location);
        // New hash after encodedPath encoding
        // encodePath with a header /
        const encodedPath = encodePath(basename + path);
        consthashChanged = getHashPath() ! == encodedPath;if (hashChanged) {
          ignorePath = path;
          // window.location.hash = encodedPath;
          pushHashPath(encodedPath);
          // history.location is current
          const prevIndex = allPaths.lastIndexOf(createPath(history.location));
          NextPaths is the latest routing stack
          const nextPaths = allPaths.slice(0, prevIndex + 1);
          nextPaths.push(path);
          allPaths = nextPaths;
          setState({ action, location });
        } else{ setState(); }}); }Copy the code

CreateLocation creates a locationObj, path is a string or object, and returns a locationObj corresponding to path.

Such as the/user/game? Name =lxfriday#haha Returns:

{
  pathname: '/user/game'.search: '? name=lxfriday'.hash: '#haha'
}
Copy the code

About search? And hash # are automatically added internally.

TransitionManager is a route jump handler that, without history.block, can be interpreted as calling the last callback from confirmTransitionTo directly with OK set to true.

CreatePath can re-spell locationObj back to a string address:

EncodePath add a slash to the basename + path concatenation string.

Next compare whether the hash changes before and after the push. GetHashPath is the hash string from the address bar of the current browser, that is, the current address.

So getHashPath ()! == encodedPath compares the current address of the browser with the address to be changed. If it changes, pushHashPath(encodedPath) applies encodedPath (the latest address) to the browser address bar. How to apply it:

function pushHashPath(path) {
  window.location.hash = path;
}
Copy the code

Yes, it’s that simple. Just change location.hash.

The next step isto modify a routing stack maintained by History itself:

const prevIndex = allPaths.lastIndexOf(createPath(history.location));
NextPaths is the latest routing stack
const nextPaths = allPaths.slice(0, prevIndex + 1);
nextPaths.push(path);
allPaths = nextPaths;
Copy the code

History. Location? Remember that the dynamics in History are old until the setState call, that is, one beat older than the location created using createLocation earlier.

So prevIndex is the last location in the routing stack before you get the push address. Then copy a new array, put the latest address at the end, and reassign it to the routing stack.

Next is setState({action, location}); Action is PUSH and location is the address that you should reach after a PUSH, so what do you do with setState? It’s pretty simple.

function setState(nextState) {
  Object.assign(history, nextState);
  // History is the object declared at the bottom, similar to window.history
  history.length = globalHistory.length;
  transitionManager.notifyListeners(history.location, history.action);
}
Copy the code

Copy the Action and location properties into the history and trigger the listener added to the transitionManager (that is, the listener for route changes).


Having written this much, I find it very difficult to describe source code in words, because after multiple wraps, a line of code may need to be examined in several places, and text descriptions inevitably leave out some details. If you want to know more, you can join my group and ask questions directly.

Article source code looks more complicated and difficult to understand, if you are interested in source code analysis, you can go to Bilibili to watch the video I recorded, in addition to download a source code to the local debugging to see the source code effect will be better oh.

  • Bilibili video address: www.bilibili.com/video/BV1GA…
  • Github analysis source address: github.com/lxfriday/so…

If you feel that you still don’t understand the article and video, you can also follow my public account and join the exchange group. If you have questions, you are welcome to exchange and discuss ~~~