I’ve been reading about Vue and React routing, and finally found that the module functions of these routing frameworks are implemented based on the browser’s native routing API. In order to trace back to the original intention, I wanted to sort out the browser’s native routing API as a whole, so as to better understand the implementation and principle of VUe-Router and React-Router.

background

The main function of the browser is to load the corresponding document in the window based on the entered URL. At the same time, the browser records all documents that have been loaded in a TAB window and provides “forward”, “backward” and “refresh” functions. So that users can switch between these recorded documents to browse and reload the current page to get the latest browsing information.

These functions were first implemented on the server side, because at that time references were not separated from the front and back ends, and page content was dynamically generated, so the jump, switch, and refresh of these pages were implemented on the server side. Later, SPA (Single Page Application) appeared. Pages were dynamically generated and loaded into the Page by JavaScript, and the latest status information of the Page could be loaded without refreshing. At this time, if you want to provide the above functions, you need to deal with them by yourself (because at this time, the pages are all in the same big frame page, there is no page jump switch), so it has spawned the Router implementation corresponding to various frames.

There are two main ways to implement front-end routing in a browser: hash, which we often use, and history, which HTML5 provides. There is another way to implement stack implementations on the Node.js server, and we’ll focus on the hash and history provided by the browser. We’ll talk more about stack implementations when we get to the x-Router source code.

Hash

URL in browser address bar, we will always find the like this address: react.docschina.org/docs/react-… React provides an address for lazy. You’ll notice that there’s an identifier starting with a # at the end of the URL, so what does that do? I’m sure they didn’t just show up for nothing.

Hash features

You can open this link directly in your browser, and you’ll notice that the page will automatically scroll to a section of the document titled React.lazy. If you scroll up the page, you’ll find some of the document. At this point, you modify the address in the address bar for react.docschina.org/docs/react-… The React. The Suspense.


In the early years, hashes were primarily used as part of urls to locate document fragments within documents. In the above example, we locate the sections of the document with the titles react.lazy and react.suspense by adding #reactlazy and #reactsuspense after the URL. So how did they do it?

In react. lazy and react. Suspense, there is an H3 tag in the title section, and the tag’s ID attribute corresponds to the hash value we entered in the URL bar (only the hash is missing).


Hash locates document fragments

Some of you might wonder: Why does hash locate the document by the ID attribute above the element? As mentioned earlier, the hash part of the URL is used to locate document fragments within the document. The only attribute that identifies the unique attribute in the document is the ID. If it were me, I would hash to match the id of the element to locate the document. Now to test our conjecture:

  • 1, the first in a new TAB window open react.docschina.org/docs/react-… Change the ID of the H3 tag to reactlazy1, append the hash value of # reactLazy to the URL bar and press Enter. The page does not locate the react. lazy section. Change the hash value of #reactlazy1 to #reactlazy1 and press Enter. This sequence shows that the hash location is still associated with the element’s ID attribute value;

  • 2, is still in a new TAB window open react.docschina.org/docs/react-… Page, then manually scroll the page to the document fragment titled React.lazy, place the mouse over the title and an icon of an anchor will appear. Click the icon found page orientation to the title as the React. Lazy document fragments and the URL address bar into a react.docschina.org/docs/react-… # reactlazy hash value. The h3 tag with the id value of reactlazy has an A tag with the href value of #reactlazy. In fact, the anchor icon on the page is the representation of the A tag. When we click on the anchor icon, we click on the A link, and then locate the URL to the H3 tag with the ID value of reactLazy, which is a good indication that the hash location is still associated with the element id value.

  • 3. The official definition of MDN is as follows:

Hash routing

In addition to locating document fragments by setting the ID of the element in the document, hash can also be set to any string to represent a route. Modern front-end frameworks such as Vue and React are equipped with routing systems to achieve fully-functional SPA applications. These routing systems all provide hash routing mode.

In hash mode, hash supports arbitrary strings to represent urls. The implementation of the hash mode for these routing systems is essentially the same: once the value of the location.hash attribute is set, the application does everything it can to detect changes in the state so that it can read the state stored in the fragment identifier and update its own state accordingly. Html5-enabled browsers will fire the hashchange event on the Window object whenever they notice that the fragment identifier has changed, which triggers the object’s functional processing logic to parse the value of location.hash, The application is then rerendered using the state information contained in this value.

Here just mentioned a basic idea, the specific implementation of the routing system, will be talked about later!

Hash event

In html5-enabled browsers, the hashchange event is triggered when the HASH value of the URL changes, and we can do some handling by listening for this event:

// Listen for the hashchange event under window
window.onhashchange = function() {
  // Prints the current hash value when the event is triggered
  console.log(window.location.hash)
}
Copy the code

In browsers that do not support HTML5, we can simulate this by listening for URL changes in 100ms polling:

(function(window){
  // If the browser does not support natively implemented events, start emulation, otherwise exit.
  if ( "onhashchange" in window.document.body ) return;
  
  var location = window.location,
      oldUrl = location.href,
      oldHash = location.hash;

  // Check whether the hash has changed every 100ms
  setInterval(function() {
    var newUrl = location.href,
        newHash = location.hash;

    // The hash has changed and the onHashchange method is registered globally (this name is to keep it consistent with the simulated event name);
    if(newHash ! == oldHash &&typeof window.onhashchange === "function"  ) {
      // Execute method
      window.onhashchange({
        type: "hashchange".oldURL: oldUrl,
        newURL: newUrl }); oldUrl = newUrl; oldHash = newHash; }},100); }) (window)
Copy the code

⚠️ Note: Setting the location.hash property updates the URL displayed in the address bar and adds a record to the browser’s history.

History

To standardize the management of browser history, HTML5 defines a relatively complex API called History.

history api

PushState () and history.replacEstate () have been added to history. Both apis accept the same arguments:

The difference between them is that the history.pushState() method adds a new state to the browser’s history, which means you can also go back to the previous page by clicking the “back” button. History.replacestate () replaces the current historical state with a new state, meaning that the “back” button will not work and the page will not “back” without more history.

⚠️ Note: When executing these two apis, the browser URL bar changes, but the page content does not refresh!

  • State Object (the state < Object | Null >) : * * a JavaScript Object, the Object contains all the information needed to restore the current document. This can be any object that can be converted to the corresponding string form using the json.stringify () method, or other specific native types such as Date and RegExp.
  • Title (the title < String | Null >) : * * browser can use its logo in the browsing history saved state, can pass an empty String, can also be introduced to a brief title, mark will enter the state.
  • Address (URL) : ** is used to indicate the location of the current state. The new URL is not necessarily an absolute path; If it is a relative path, it is benchmarked against the current URL (hash like #reactlazy); The incoming URL should be homologous with the current URL, otherwise pushState() will throw an exception. This parameter is optional; If not specified, the current URL of the document.

To this end, we can use the language finch website to do a series of experiments:

window.history.pushState(null.null."https://www.yuque.com/dashboard/?name=littleLane");
// result: https://www.yuque.com/dashboard/?name=littleLane

window.history.pushState(null.null."https://www.yuque.com/dashboard/name/littleLane");
//result: https://www.yuque.com/dashboard/name/littleLane

window.history.pushState(null.null."? name=littleLane");
//result: https://www.yuque.com/dashboard?name=littleLane

window.history.pushState(null.null."name=littleLane");
//result: https://www.yuque.com/dashboard/name=littleLane

window.history.pushState(null.null."/name/littleLane");
//result: https://www.yuque.com/dashboard/name/littleLane
Copy the code

When we execute the above statement in the console, the browser URL changes to the result we commented on, but the page is not rerendered, and each time we pushState, a record is added to the browser’s history, which can be viewed using the “back” button. After executing the above test statement, you can replace pushState with replaceState and run the test again. In this case, the new browsing history will be replaced by the current history, which can be viewed by using the “back” button.

⚠️ Note: The URL here is not cross-domain and we get an error when we change www.yuque.com to www.baidu.com.

2. In addition to the new API above, the history object also has a Length attribute that represents the number of browsing history lists, and defines methods for switching browsing history between back(), forward(), and go().

The back() and forward() methods of the History object function like the browser’s back and forward buttons: they allow the browser to jump back or forward one space in the History. The go() method, on the other hand, accepts an integer and can skip any number of pages in the browse history list either forward (positive integer arguments) or backward (negative integer arguments). For example, history.go(-1) will jump back one page, history.go(0) will refresh the current page, history.go(1) will jump forward one page.

History event – popState

When the user views the saved history through the forward and Back buttons, the browser fires a PopState event on the Window object. The event object associated with this event has a state property that contains a copy of the state object passed to the pushState() method.

// Listen for the onPopState event at window
window.onpopstate = function(state) {
  // Outputs the current state when the onPopState event (the user toggles browsing history with the "forward" and "back" buttons) is triggered
  console.log(state)
}
Copy the code

Location

The location property of the Window object and the location property of the Document object refer to the Location object, which represents the URL of the Document currently displayed in the Window, and defines methods to make the Window load new documents.

window.location === document.location  // Always return true
Copy the code

Parsing the URL

The href attribute of the Location object is a string representing the full text of the current URL. The toString() method of the Location object returns the value of the href attribute, so you can use Location instead of location.href in cases where toString() is implicitly called.

The object’s protocol, host, hostname, port, PathName, and search represent parts of the URL, and are therefore called URL decomposition properties. Generally we use more is to extract the URL inside the parameter:

// Get address bar parameters
const getUrlParame = (paramName) = > {
    const urlParams = {};
    let params = window.location.search.substring(1);
    if(! params) {return;
    }
    params = params.split('&');
    for (let i = 0; i < params.length; i += 1) {
        let item = params[i];
        item = item.split('=');
        urlParams[item[0]] = decodeURIComponent(item[1]);
    }
    if (paramName) {
        return urlParams[paramName];
    }
    return urlParams;
};
Copy the code

Loading a new document

The Assign () method of the Location object causes the window to load and display the document in the specified URL. The replace() method has a similar function, but it removes the current document from the browsing history before the new document loads, meaning that the back button does not take the browser to the original document.

The Location object also defines reloadable () methods to reload the current document.

conclusion

Hash and History are two different routing modes supported by browsers, and their respective features, apis, and events are discussed in detail. Location is one of the most important objects in browser routing. This series of content for our subsequent understanding of vue-Router, React-Router and other routing systems implementation and read the source code laid a solid foundation.