简单记录学习single-spa的过程(4)

712 阅读3分钟

接上回

  • props的实现,路由的实现?

这次的问题非常明确,首先还是老套路,找方法然后看进去~

路由

文档中路由相关的方法navigateToUrl(...),首先从文档中看,主要就是一个路由跳转方法,但是如何操作对应的应用切换的。我们跟着看进去~

/src/navigation/navigation-events.js

export function navigateToUrl(obj) {
  let url;
  // 获取url的值
  if (typeof obj === "string") {
    url = obj;
  } else if (this && this.href) {
    url = this.href;
  } else if (
    obj &&
    obj.currentTarget &&
    obj.currentTarget.href &&
    obj.preventDefault
  ) {
    url = obj.currentTarget.href;
    obj.preventDefault();
  } else {
	... // 错误处理
  }
  // 分别创建对应的a标签的DOM对象
  const current = parseUri(window.location.href);
  const destination = parseUri(url);
  // hash的情况
  if (url.indexOf("#") === 0) {
    window.location.hash = destination.hash;
  } else if (current.host !== destination.host && destination.host) {
    if (process.env.BABEL_ENV === "test") {
      return { wouldHaveReloadedThePage: true };
    } else {
      window.location.href = url;
    }
  } else if (
    destination.pathname === current.pathname &&
    destination.search === current.search
  ) {
    window.location.hash = destination.hash;
  } else {
    // different path, host, or query params
   	/*
    * 上面提到说不同path,host 或者是 search的情况, 不同host的话指的应该也是没有 host 的情况所以这边用了,pushState这个方法
    */
    window.history.pushState(null, null, url);
  }
}
// 以传入地址为href创建一个a标签对象,这里应该是想借DOM对象对于URI的解析,来进行格式化取值
function parseUri(str) {
  const anchor = document.createElement("a");
  anchor.href = str;
  return anchor;
}

那么首先看完这里以后,发现在路由的这个方法中有两种切换路由的方式相当于,一种是直接修改hash 一种是 通过pushState这个方法进行的路由变换,有一种很熟悉的感觉有没有。我感觉就很像VueRouter的原理,当然可能大部分现在spa的路由都是通过这个原理去实现的。然后我们看到下面这一段

/src/navigation/navigation-events.js


// 发布中心, 收集事件数组
const capturedEventListeners = {
  hashchange: [],
  popstate: [],
};

// 路由的监听事件,对这两个的进行额外的收集处理
export const routingEventsListeningTo = ["hashchange", "popstate"];

if (isInBrowser) {
  window.addEventListener("hashchange", urlReroute);
  window.addEventListener("popstate", urlReroute);

  // Monkeypatch addEventListener so that we can ensure correct timing
  const originalAddEventListener = window.addEventListener;
  const originalRemoveEventListener = window.removeEventListener;
  // 重写事件监听,针对hashchange,popstate进行收集,防止挂载的路由事件发生问题,其他事件按原本的进行挂载
  window.addEventListener = function (eventName, fn) {
    if (typeof fn === "function") {
      if (
        routingEventsListeningTo.indexOf(eventName) >= 0 &&
        !find(capturedEventListeners[eventName], (listener) => listener === fn)
      ) {
        capturedEventListeners[eventName].push(fn);
        return;
      }
    }

    return originalAddEventListener.apply(this, arguments);
  };
  // 原理同上
  window.removeEventListener = function (eventName, listenerFn) {
    if (typeof listenerFn === "function") {
      if (routingEventsListeningTo.indexOf(eventName) >= 0) {
        capturedEventListeners[eventName] = capturedEventListeners[
          eventName
        ].filter((fn) => fn !== listenerFn);
        return;
      }
    }

    return originalRemoveEventListener.apply(this, arguments);
  };

  // 重写pushState方法,使其兼容IE11
  window.history.pushState = patchedUpdateState(
    window.history.pushState,
    "pushState"
  );
  window.history.replaceState = patchedUpdateState(
    window.history.replaceState,
    "replaceState"
  );

  // 单例模式报错,全局只注册一个singleSpaNavigate
  if (window.singleSpaNavigate) {
	... // 错误处理
  } else {
    /* For convenience in `onclick` attributes, we expose a global function for navigating to
     * whatever an <a> tag's href is.
     */
    window.singleSpaNavigate = navigateToUrl;
  }
}

// 重写对应state的方法
function patchedUpdateState(updateState, methodName) {
  return function () {
    const urlBefore = window.location.href;
    const result = updateState.apply(this, arguments);
    const urlAfter = window.location.href;

    if (!urlRerouteOnly || urlBefore !== urlAfter) {
      urlReroute(createPopStateEvent(window.history.state, methodName));
    }

    return result;
  };
}

// 为收集的 popstate 回调函数列表创建事件对象,并做区分singleSpa = true
function createPopStateEvent(state, originalMethodName) {
  let evt;
  try {
    evt = new PopStateEvent("popstate", { state });
  } catch (err) {
    // IE 11 compatibility https://github.com/single-spa/single-spa/issues/299
    // https://docs.microsoft.com/en-us/openspecs/ie_standards/ms-html5e/bd560f47-b349-4d2c-baa8-f1560fb489dd
    evt = document.createEvent("PopStateEvent");
    evt.initPopStateEvent("popstate", false, false, state);
  }
  evt.singleSpa = true;
  evt.singleSpaTrigger = originalMethodName;
  return evt;
}

// 重新渲染页面
function urlReroute() {
  // 文章(2)就提到了的重新渲染页面的方法,这里就不重新介绍了
  reroute([], arguments);
}

这里主要做了监听,依赖收集,重写方法,这么几个事情再结合之前的页面渲染方法就完成了,对应的路由导航了~,

props

关于props的问题,目前看下来,只有两个地方会涉及到props一个是reasonableTime(...) 一个是 Parcel中注册的update生命周期,也就是说Props针对Application是不会发生动态变化的情况。那么换而言之,我们针对single-spa在开发的过程中,还要去考虑应用间的状态通信的开发,当然也可以用一些现有的开源框架解决。

总结

到这里我们基于这一个小系列就要结束了,这个系列简单的带大家走读了single-spa框架中的一些简单实现,实战方面后续会出一个系列专门讲到如应用singles-spa搭建自己的微应用,后续还会推出一个系列是关于qiankun的简单学习介绍,有兴趣的同学,千万不要错过了,我们回见~