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
相关逻辑。上篇文章有讲到过,主要是通过控制状态来达到激活和切换子应用的效果。
路由切换的时候怎么进行微应用加载的?
实现原理:通过监听和重写浏览器的hashchange
、popstate
、pushState
和replaceState
的方法
下面这段代码是默认执行的,说明一开始就对浏览器中的方法进行了注册和重写,hash
模式的代码会重写hashchange
方法,当使用history
模式时,pushState
和 replaceState
事件被触发时不会触发 popstate
事件,只有back
、forward
、go
才可以触发。
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,如果urlBefore
和urlAfter
不同的时候才会重写路由,否则就只要进行pushState
和replaceState
操作就会调用。
只有子应用启动了之后才手动触发popState
事件,因为在没有启动子应用之前就可以使用默认的路由事件了。
dispatchEvent
即为派发事件,实际就是我们在开始时监听了popstate
事件,回调函数即reroute
,因此pushstate
和replacestate
执行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;
}
为什么要重写pushState
和 replaceState
方法?
除了在微前端框架中需要监听对应的导航事件外,在微前端框架外部我们也可以通过 addEventListener
的方式来注册 hashchange
和 popstate
事件,那么这样一来导航事件就会有多个,为了在实现对导航事件的控制,达到路由变化时对应的子应用能够正确的 卸载 和 挂载,需要对 addEventListener
注册的 hashchange
和 popstate
进行拦截,并将对应的事件给存储起来,便于后续在特定的时候能够实现手动触发。
发现在挂载和卸载阶段都调用了callAllEventListeners
方法,而callAllEventListeners
又调用了callCapturedEventListeners
方法,callCapturedEventListeners
方法中是对"hashchange", "popstate"挨个进行手动调用。
这证明了加载子应用时通过reroute和路由控制不断地在加载和切换子应用。
callCapturedEventListeners
方法源码参考上面
网友评论