文章转发自 alili.tech前端
用这句话来解释,微前端的路由,再合适不过来.react
路由分发式微前端,即经过路由将不一样的业务分发到不一样的、独立前端应用上。其一般能够经过 HTTP 服务器的反向代理来实现,又或者是应用框架自带的路由来解决。 就当前而言,经过路由分发式的微前端架构应该是采用最多、最易采用的 “微前端” 方案。可是这种方式看上去更像是多个前端应用的聚合,即咱们只是将这些不一样的前端应用拼凑到一块儿,使他们看起来像是一个完整的总体。可是它们并非,每次用户从 A 应用到 B 应用的时候,每每须要刷新一下页面。 -- 引用自phodal 微前端的那些事儿git
在模块加载器那一章的示例代码,已经很是充分了展现了路由分发应用的步骤.github
在单页面前端的路由,目前有两种形式, 一种是全部主流浏览器都兼容多hash路由, 基本原理为url的hash值的改变,触发了浏览器onhashchange事件,来触发组件的更新redux
还有一种是高级浏览器才支持的 History API, 在 window.history.pushState(null, null, "/profile/");
的时候触发组件的更新浏览器
// hash 模式,项目路由用的是hash模式会用到该函数
export function hashPrefix(app) {
return function (location) {
let isShow = false
//若是该应用 有多个须要匹配的路劲
if(isArray(app.path)){
app.path.forEach(path => {
if(location.hash.startsWith(`#${path}`)){
isShow = true
}
});
}
// 普通状况
else if(location.hash.startsWith(`#${app.path || app.url}`)){
isShow = true
}
return isShow;
}
}
// pushState 模式
export function pathPrefix(app) {
return function (location) {
let isShow = false
//若是该模块 有多个须要匹配的路径
if(isArray(app.path)){
app.path.forEach(path => {
if(location.pathname.indexOf(`${path}`) === 0){
isShow = true
}
});
}
// 普通状况
else if(location.pathname.indexOf(`${app.path || app.url}`) === 0){
isShow = true
}
return isShow;
}
}
// 应用注册
export async function registerApp(params) {
// 第三个参数为,该模块是否显示
singleSpa.registerApplication(params.name, // 模块名字
() => SystemJS.import(params.main), // 模块渲染的入口文件
params.base ? (() => true) : pathPrefix(params) // 模块显示的条件
);
}
复制代码
当url前缀,与配置中的url前缀保持一致的时候, singleSpa会激活对应的模块,而后把模块内容渲染出来.前端框架
在模块被激活的时候,模块会读取url,再渲染到对的页面.服务器
这就是微前端路由的路由工做流程架构
在目前全部支持spa的前端框架中,都支持了Hash路由. Hash路由都工做大体原理就是: url的Hash值的改变,触发了浏览器onhashchange事件,进而来触发组件的更新. 全部的前端的框架,都是基于onhashchange来更新咱们的页面的. 当咱们的架构使用微前端的话,若是选择hash路由,即可以保证全部的前端技术框架的更新事件都是一致的. 因此使用Hash路由也是最省心的.若是不介意Hash路由中url的 #
字符,在微前端中使用Hash也是推荐的.app
你们都知道,HTML5中History对象上新增了两个API (pushState与replaceState)
. 在这两个新API的做用下,咱们也是能够作到页面无刷新,而且更新页面的.而且url上不须要出现#
号. 保持了最高的美观度(对于一些人来说). 固然如今几乎全部的主流SPA技术框架都支持这一特性. 可是问题是,这两个API在触发的时候,是没有一个全局的事件触发的. 多种技术框架对History路由的实现都不同,就算是技术栈都是 React,他的路由都有好几个版本.
那咱们如何保证一个项目下,多个技术框架模块的路由作到协同呢?
前提: 假设咱们全部的项目用的都是React,咱们的路由都在使用着同一个版本.
思路: 咱们是能够这样作的,在咱们的base前端模块(由于他老是第一个加载,也是永远都不会被销毁的模块)中的Store.js, 实例化一个React router的核心库history
,经过消息总线,把这个实例传入到全部的模块中. 在每一个模块的路由初始化的时候,是能够自定义本身的history的.把模块的history从新指定到传入的history. 这样就能够作到,全部模块的路由之间的协同了. 由于当页面切换的时候,history触发更新页面的事件,当全部模块的history都是一个的时候,全部的模块都会更新到正确的页面. 这样就保证了全部模块与路由都协同.
若是你看不懂我在讲什么,直接贴代码吧:
//Base前端模块的 Store.js
import { createStore, combineReducers } from 'redux'
// react router 的核心库 history
import createHistory from 'history/createBrowserHistory'
const history = createHistory()
// 传出去
export const storeInstance = createStore(combineReducers({ namespace: () => 'base' ,history }))
复制代码
// 应用注册
export async function registerApp(params) {
...
// history 直接引入进来,用systemjs直接导入实例
try {
storeModule = params.store ? await SystemJS.import(params.store) : { storeInstance: null };
} catch (e) {
...
}
...
// 跟派发器一块儿放进 customProps 中
customProps = { store: storeModule, globalEventDistributor: ... };
// 在注册的时候传入 customProps
singleSpa.registerApplication(params.name,
() => SystemJS.import(params.main),
params.base ? (() => true) : pathPrefix(params),
customProps // 应用注册的时候,history会包含在 customProps 中,直接注入到模块中
);
}
复制代码
// React main.js
import React from 'react'
import ReactDOM from 'react-dom'
import singleSpaReact from 'single-spa-react'
import RootComponent from './root.component'
const reactLifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: (spa) => {
// 在这里,把history传入到组件
return <RootComponent history={spa.customProps.history}/> }, domElementGetter: () => document.getElementById('root') }) ... 复制代码
// RootComponent
import React from 'react'
import { Provider } from 'react-redux'
export default class RootComponent extends React.Component {
render() {
return <Provider store={this.state.store}> // 在这里从新指定Router的history <Router history={this.props.history}> <Switch> ... </Switch> </Router> </Provider>
}
}
复制代码
以上就是让全部模块的路由协同,保证只有一个history的用法
问题: 用上面的方式是可行的,可是遗憾的是,他的应用场景比较小,只能在单一技术栈,单一路由版本的状况下使用. 微前端最大的优点之一就是自由选择技术栈. 在一个项目中,使用多个适合不一样模块的技术栈.
思路: 咱们实际上是能够经过每个模块对外输出一个路由跳转到接口,基于消息总线的派发,让每个模块渲染到正确的页面. 好比 模块A要跳转到 /a/b/c
,模块a先更新到/a/b/c
路由的页面,而后经过消息总线,告诉全部模块,如今要跳转到 /a/b/c
了. 而后其余模块,有/a/b/c
这个路由都,就直接跳转,没有的就什么都不作.
咱们能够这样作:
// Store.js
import { createStore, combineReducers } from 'redux'
import createHistory from 'history/createBrowserHistory'
const history = createHistory()
// 对外输出一个to的接口,当一个模块须要跳转界面的时候,会向全部的模块调用这个接口,
// 而后对应的模块会直接渲染到正确的页面
function to(state, action) {
if (action.type !== 'to' ) return { ...state, path: action.path }
history.replace(action.path)
return { ...state, path: action.path }
}
export const storeInstance = createStore(combineReducers({ namespace: () => 'base', to }))
export { history }
复制代码
这是路由跟消息总线的一种完美结合的使用方式,消息总线的潜力还有不少,后续会慢慢说明. 未完待续 ...