背景
在2020年时,目前的公司因为业务架构的调整,前端更加适合微前端架构,从而保障在一个入口中可以访问其他业务系统,并且拥有较好的用户体验。
这里不在赘述微前端的概念,网上相关的资料有很多(目前我们是从qiankun切换到了wujie)
随着业务的发展,模块拆分越来越细,就导致很多子系统中拥有相同的功能模块 ,以及依赖相同的数据 ,这些数据大都是存放在主容器 的vuex
中,熟悉vue
开发的同学肯定想到vuex
具有响应式,只需要考虑获取即可数据更新视图会同步更新,所以为了子系统可以共享vuex,我们需要自己封装一些数据,具体操作如下:
设计思路
利用vuex
管理数据最主要是数据可以响应式,但是以往只能在当前系统中触发响应式,这篇文章主要告诉你如何在多个地方触发响应式,达到所有系统都可以监听数据的效果。
前提
- 框架为
vue2.7.x
。 - 使用
vuex
管理共享数据(不仅仅是数据)。 - 微前端框架为
wujie
(qiankun
也是类似的)
首先
将全局共享的数据、API放在vuex.common(vuex支持module)
中,然后将整个store进行导出,这样子系统不仅仅可以共享数据,监听数据,还可以共享一些数据操作的方法(actions, mutations
)等等。
js
// common.js
export default {
state: {
list: []
},
getters: {
newList (store) {
store.list.sort()
}
},
actions: {
getList ({}, payload) {
return {}
}
},
mutations: {
SET_LIST (store, payload) {
store.list = payload
}
}
}
// store/index.js
// 利用vuex.modules划分出公共的store
import common from '@/store/model/common'
export default new Vuex.Store({
state: {
httpConfig: {},
loaded: false
},
modules: {
common
}
其次
将该store模块
通过微前端框架提供的prop | window变量
传递给子系统的运行环境
js
// commonStore.js
import common from '@/store/model/common'
// 声明传递给子系统的公共store,内部包裹的是common.js
class GaiaStore {
constructor (options) {
Object.defineProperty(options._vue.prototype, '$GAIA_STORE', {
get: function get () {
return this._GAIA_STATE
}
})
this.GAIA_STATE = options.GAIA_STATE
this.GAIA_GETTERS = options.GAIA_GETTERS
options._vue.mixin({
beforeCreate () {
if (this.$options.gaiaStore) {
this._GAIA_STATE = this.$options.gaiaStore
options._vue.util.defineReactive(this, '_GAIA_STATE', this._GAIA_STATE)
} else {
this._GAIA_STATE = (this.$parent && this.$parent._GAIA_STATE) || this
}
}
})
}
}
function replaceStore (obj, type) {
const result = {}
Object.keys(obj).forEach(k => {
result[k] = async function (val) {
if (type === 'dispatch') {
return new Promise((resolve, reject) => {
global[type](k, val)
.then(res => resolve(res))
.catch(err => reject(err))
})
} else {
return global[type](k, val)
}
}
})
return result
}
// 定义传入子应用的数据
export default {
state: common.state,
getters: common.getters,
actions: replaceStore(common.actions, 'dispatch'),
mutations: replaceStore(common.mutations, 'commit'),
func: {
rootWindow: window
},
utils: {
// 绑定state
replaceState,
// 公共store
GaiaStore
}
}
js
// index.jsx
在wujie中进行导入
import commonStore from './commonStore'
setupApp({
name: app.name,
url: app.entry + app.routerPrex,
exec: true,
props: commonStore
})
最后
在子系统初始化的时候,利用当前的vue
实例初始化该变量即可,
js
// 子系统入口文件,一般为main.js
import Vue from 'vue'
import App from './App.vue'
// 初始化GaiaStore
function bootstrap ({ actions, mutations, state, getters, func, utils } = {}) {
// 将父容器的common绑定在当前vue实例中,从而达到子系统调用父系统的action也能触发响应式
utils.replaceState(actions, 'GAIA_COM_ACTIONS', Vue.prototype)
utils.replaceState(mutations, 'GAIA_COM_MUTATIONS', Vue.prototype)
// 初始化GaiaStore,绑定响应式数据
gaiaStore = new utils.GaiaStore({
_vue: Vue,
GAIA_STATE: state || {},
GAIA_GETTERS: getters || {}
})
// 注入全局容器函数
window.func = func
}
function mount ({ utils } = {}) {
router = new VueRouter({
mode: 'history',
base: APP_ROUTER_PREFIX,
routes
})
new Vue ({
router,
store,
gaiaStore,
render: (h) => h(App)
}).$mount('#app')
}
unmount () {}
// 初始化工作
bootstrap(window.$wujie.props)
window.__WUJIE_MOUNT = () => {
mount(window.$wujie.props)
}
window.__WUJIE_UNMOUNT = () => {
unmount(window.$wujie.props)
}
使用
以上原理跟vue-router
的原理差不多:
- 通过全局
mixins
添加函数挂载全局变量。 - 通过
vue.util.defineReactive
声明响应式。 - 将该变量挂载至
vue
实例中。
vue
<template>
<div class="page"> </div>
</template>
<script>
export default {
computed: {
list () {
// 获取common.state.list
return this.$GAIA_STORE.GAIA_STATE.list
},
newList () {
// 获取common.getters.newList
return this.$GAIA_STORE.GAIA_GETTERS.newList
}
},
watch: {
list () {
// 监听父容器的list
}
},
created () {
// 调用 common.mutations.SET_CATCH_COMPONENT
this.GAIA_COM_MUTATIONS.SET_CATCH_COMPONENT()
// 调用 common.actions.actions.getTableConfig
this.GAIA_COM_ACTIONS.getTableConfig()
}
}
</script>
<style scoped lang="scss">
</style>
总结
以上就是如何在微前端中共享vuex
的方法
同理公共的模块、api
等等也可以这样传递,相比与封装sdk而言,少了维护sdk
版本的问题,同时也可以满足一部分共享模块、api
的需求。
为什么从qiankun切换到wujie
qiankun
没有实现完美的沙箱,目前在切换的时候css隔离相对不够彻底。wujie
采用iframe
运行时,webcomponent
挂载dom
的方式,避免了js
污染和css
污染的问题- 相比于
qiankun的
多个js
沙箱,个人感觉iframe
的沙箱更为牢靠。 - 用于尝试新的技术,才能有所收获。
最后
微前端没有银弹 各有各的优点,选择适合自己项目的框架即可,反正都要做一些适配。