美文网首页js css html
123.路由重写-reroute

123.路由重写-reroute

作者: wo不是黄蓉 | 来源:发表于2022-11-21 21:49 被阅读0次
    路由重写-reroute.jpeg

    reroute 方法,没有调用start时进行加载app,调用loadApps方法,loadApps中准备加载应用,准备完毕后注册自定义事件callAllEventListeners

    function reroute() {
          if (isStarted()) {
            appChangeUnderway = true;
            appsThatChanged = appsToUnload.concat(
              appsToLoad,
              appsToUnmount,
              appsToMount
            );
            return performAppChanges();
          } else {
            appsThatChanged = appsToLoad;
            return loadApps();
          }
          function loadApps() {
            return Promise.resolve().then(function () {
              var loadPromises = appsToLoad.map(toLoadPromise);
              return (
                Promise.all(loadPromises)
                  .then(callAllEventListeners)
                  .then(function () {
                    return [];
                  })
                  .catch(function (err) {
                    callAllEventListeners();
                    throw err;
                  })
              );
            });
          }
    }
    

    callAllEventListeners方法,遍历每个应用,给每个应用都添加callCapturedEventListeners事件监听,这边是操作的备份对象不会对浏览器中的事件进行重写。

      function callAllEventListeners() {
        pendingPromises.forEach(function (pendingPromise) {
          callCapturedEventListeners(pendingPromise.eventArguments);
        });
        callCapturedEventListeners(eventArguments);
      }
    

    callCapturedEventListeners方法,遍历快照中重写的方法,之后监听到"hashchange", "popstate"时就会走我们自己定义的方法逻辑。

    var routingEventsListeningTo = ["hashchange", "popstate"];
    function callCapturedEventListeners(eventArguments) {
      var _this = this;
      if (eventArguments) {
        var eventType = eventArguments[0].type;
        if (routingEventsListeningTo.indexOf(eventType) >= 0) {
          capturedEventListeners[eventType].forEach(function (listener) {
            try {
              // The error thrown by application event listener should not break single-spa down.
              // Just like https://github.com/single-spa/single-spa/blob/85f5042dff960e40936f3a5069d56fc9477fac04/src/navigation/reroute.js#L140-L146 did
              listener.apply(_this, eventArguments);
            } catch (e) {
              setTimeout(function () {
                throw e;
              });
            }
          });
        }
      }
    }
    

    这个流程完毕之后,主应用调用了start方法后,就开始走performAppChanges相关逻辑。上篇文章有讲到过,主要是通过控制状态来达到激活和切换子应用的效果。

    路由切换的时候怎么进行微应用加载的?

    实现原理:通过监听和重写浏览器的hashchangepopstatepushStatereplaceState的方法

    下面这段代码是默认执行的,说明一开始就对浏览器中的方法进行了注册和重写,hash模式的代码会重写hashchange方法,当使用history模式时,pushStatereplaceState事件被触发时不会触发 popstate事件,只有backforwardgo才可以触发。

    pushState:只会向历史堆栈里面添加一个状态,不会更新页面,传了url不同源时会报错,不传则将其设置为当前文档的url。

    replaceState:修改当前历史记录实体 ,同上,传了url不同源时会报错,不传则将其设置为当前文档的url。

    //判断是否在浏览器环境
    if (isInBrowser) {
      window.addEventListener("hashchange", urlReroute);
      window.addEventListener("popstate", urlReroute);
    
      // 不要直接操作原有方法,在copy的方法上操作
      var originalAddEventListener = window.addEventListener;
      var originalRemoveEventListener = window.removeEventListener;
    //重写pushState方法
      window.history.pushState = patchedUpdateState(
        window.history.pushState,
        "pushState"
      );
        //重写replaceState方法
      window.history.replaceState = patchedUpdateState(
        window.history.replaceState,
        "replaceState"
      );
    }
    

    patchedUpdateState,记录跳转前和跳转后的url,如果urlBeforeurlAfter不同的时候才会重写路由,否则就只要进行pushStatereplaceState操作就会调用。

    只有子应用启动了之后才手动触发popState事件,因为在没有启动子应用之前就可以使用默认的路由事件了。

    dispatchEvent即为派发事件,实际就是我们在开始时监听了popstate事件,回调函数即reroute,因此pushstatereplacestate执行reroute的本质上是通过popstate来触发route

    function patchedUpdateState(updateState, methodName) {
      return function () {
          //性能优化,只有当当前页面和要跳转的页面不一样时才创建createPopStateEvent
          //获取跳转前的url
        var urlBefore = window.location.href;
        var result = updateState.apply(this, arguments);
          //获取跳转后的url
        var urlAfter = window.location.href;
        if (!urlRerouteOnly || urlBefore !== urlAfter) {
          if (isStarted()) {
            window.dispatchEvent(
              createPopStateEvent(window.history.state, methodName)
            );
          } else {
            reroute([]);
          }
        }
        return result;
      };
    }
    

    createPopStateEvent方法,将重写后的popstate返回

    function createPopStateEvent(state, originalMethodName) {
      var evt;
      try {
        evt = new PopStateEvent("popstate", {
          state: state,
        });
      } catch (err) {
        evt = document.createEvent("PopStateEvent");
        evt.initPopStateEvent("popstate", false, false, state);
      }
      evt.singleSpa = true;
      evt.singleSpaTrigger = originalMethodName;
      return evt;
    }
    

    为什么要重写pushStatereplaceState方法?

    这篇文章对这个问题的解释我觉得是最合理的

    除了在微前端框架中需要监听对应的导航事件外,在微前端框架外部我们也可以通过 addEventListener 的方式来注册 hashchangepopstate 事件,那么这样一来导航事件就会有多个,为了在实现对导航事件的控制,达到路由变化时对应的子应用能够正确的 卸载挂载,需要对 addEventListener 注册的 hashchangepopstate 进行拦截,并将对应的事件给存储起来,便于后续在特定的时候能够实现手动触发。

    发现在挂载和卸载阶段都调用了callAllEventListeners方法,而callAllEventListeners又调用了callCapturedEventListeners方法,callCapturedEventListeners方法中是对"hashchange", "popstate"挨个进行手动调用。
    这证明了加载子应用时通过reroute和路由控制不断地在加载和切换子应用。

    callCapturedEventListeners方法源码参考上面

    相关文章

      网友评论

        本文标题:123.路由重写-reroute

        本文链接:https://www.haomeiwen.com/subject/ootgxdtx.html