1. Not frompushStateandpopstateSpeaking of

The history mode of routing is implemented based on pushState and PopState.

Please send pushState and popState.

Note that just calling history.pushState() or history.replaceState() won’t trigger a popstate event. The popstate event will be triggered by doing a browser action such as a click on the back or forward button (or calling history.back() or history.forward() in JavaScript).

2. The route idea of native JS implementation

  • callpushState: Modifies only the current onehistoryThe contents of the stack will not be navigated to a new page unless refreshed,
location.href // "https://juejin.cn/editor/drafts/69724xxxxxx02504968"
history.state // null
history.pushState({id: 1}, 'id1'.'/a')
location.href // "https://juejin.cn/a"
history.state // {id: 1}
Copy the code
  • How can that bepushStateWhat about updating pages? Repackage the method and also provide a mechanism for updating the page within the method to be constructed.
  function push(url) {
    try {
      // We use try here because we can pass absolute paths to urls with pushState, but we can get an error across domains
      history.pushState({}, ' ', url)
    } catch (e) {
      location.assign(url)
    }
    updatePage()
  }
  function updatePage(location) {      
    // Manipulate dom based on the current location
  }
Copy the code

Here we use observer mode to rewrite the update logic:

  // Borrow from the history library
  const MyRouter = function() {
    // Maintain a list of listeners
    const listeners = [] 
    // Add a listener
    function listen(listener) {
      listeners.push(listener)      
      return function() {
        listeners.filter(s= >s ! == listen) } }// Execute all listening events
    function call(. args) {
      listeners.forEach(listener= >listener(... args)) }return {
        listen,
        call
    }    
  }
  
 const { listen, call } = myRouter()
Copy the code

So we can rewrite the push method and change updatePage to Call so that we can inject update logic and call it when we push.

So when do you inject listeners?

We use native writing to directly inject the page when it loads:

  listen(function(location) {
     // Render the corresponding content according to the current path
     updatePage(location)
  })
Copy the code

The react-router-dom browser-router is the update logic injected by constructor. Browser-router defines the state of the location, and the logic to listen for updates is simple:

 listen((location) = > {
   this.setState({ location })
 })
 
 // When pushing, a call is called to iterate over all listener executions,
 // This. SetState will be updated to re-render 
      
Copy the code
  • thepopstateFor what?

The popState event can be triggered only when the operation of the address bar has been changed. In other words, the address in the address bar at this time is the previous address. At this time, we can update to the corresponding page according to the current address.

 // This function can be wrapped directly into myRouter logic
 window.addEventListener("popstate".function (event) {
   call(location);
 });
Copy the code
  1. The complete code
<! DOCTYPE html><html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Document</title>
    <style>
      a {
        margin-right: 20px;
      }
    </style>
  </head>
  <div class="link-wrapper">
    <a href="javascript:;" data-href="/home">home</a>
    <a href="javascript:;" data-href="/abc">abc</a>
    <a href="javascript:;" data-href="/def">def</a>
    <a href="javascript:;" data-href="/ghi">ghi</a>
  </div>
  <div class="content"></div>
  <body>
    <script>
      const history = window.history;
      const createSomeElement = (textContent) = > {
        const div = document.createElement("div");
        div.textContent = textContent;
        return div;
      };
      // Silver mapping of path and routing components
      const pages = {
        "/home": createSomeElement("home"),
        "/abc": createSomeElement("abc"),
        "/def": createSomeElement("def"),
        "/ghi": createSomeElement("ghi")};const IfRedirectPath = () = > {
        const pagesId = Object.keys(pages);
        if (path == "/"| |! pagesId.includes(path)) { path ="/home";
          history.pushState({}, ""."/home");          
        }
        return path;
      }
    
      const renderPage = (path) = > {
        // Redirect logic
        path = IfRedirectPath(path);
        const cont = document.querySelector(".content");
        const firstChild = cont.firstChild;
        if (firstChild) {
          cont.removeChild(firstChild);
        }
        cont.appendChild(pages[path]);
      };

      const myRouter = function () {
        const handlers = [];
        const listen = (listener) = > {
          handlers.push(listener);
          return function unlisten() {
            handlers.filter((h) = >h ! == listener); }; };const call = (location) = > {
          handlers.forEach((cb) = > cb(location));
        };

        window.addEventListener("popstate".function () {
          call({ url: window.location.pathname });
        });

        return {
          listen,
          call
        };
      };

      const { listen, call } = myRouter();

      const push = (url, state = {}, title) = > {
        try {
          history.pushState(state, title, url);
        } catch (e) {
          window.assign(url);
        }
        call({ state, title, url });
      };

      window.addEventListener("beforeunload".function () {
        unListen && unListen();
      });

      document
        .querySelector(".link-wrapper")
        .addEventListener("click".function (e) {
          if(e.target.tagName ! = ="A") return;
          push(e.target.dataset.href);
        });

      var unListen = listen(function (loc) {
        renderPage(loc.url);
      });

      window.addEventListener("load".function () {
        const path = window.location.pathname;
        push(path);
      });
    </script>
  </body>
</html>

Copy the code

The complete code