likes
comments
collection
share

在微前端中,如何通过复用vuex达到共享数据的目的。

作者站长头像
站长
· 阅读数 12

背景

在2020年时,目前的公司因为业务架构的调整,前端更加适合微前端架构,从而保障在一个入口中可以访问其他业务系统,并且拥有较好的用户体验。

这里不在赘述微前端的概念,网上相关的资料有很多(目前我们是从qiankun切换到了wujie)

随着业务的发展,模块拆分越来越细,就导致很多子系统中拥有相同的功能模块,以及依赖相同的数据,这些数据大都是存放在主容器vuex中,熟悉vue开发的同学肯定想到vuex具有响应式,只需要考虑获取即可数据更新视图会同步更新,所以为了子系统可以共享vuex,我们需要自己封装一些数据,具体操作如下:

设计思路

利用vuex管理数据最主要是数据可以响应式,但是以往只能在当前系统中触发响应式,这篇文章主要告诉你如何在多个地方触发响应式,达到所有系统都可以监听数据的效果。

前提

  1. 框架为vue2.7.x
  2. 使用vuex管理共享数据(不仅仅是数据)。
  3. 微前端框架为wujieqiankun也是类似的)

首先

将全局共享的数据、API放在vuex.common(vuex支持module)中,然后将整个store进行导出,这样子系统不仅仅可以共享数据,监听数据,还可以共享一些数据操作的方法(actions, mutations )等等。

// 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变量传递给子系统的运行环境

// 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
  }
}

// index.jsx
在wujie中进行导入
import commonStore from './commonStore'
setupApp({
  name: app.name,
  url: app.entry + app.routerPrex,
  exec: true,
  props: commonStore
})

最后

在子系统初始化的时候,利用当前的vue实例初始化该变量即可,

// 子系统入口文件,一般为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的原理差不多:

  1. 通过全局mixins添加函数挂载全局变量。
  2. 通过vue.util.defineReactive声明响应式。
  3. 将该变量挂载至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

  1. qiankun没有实现完美的沙箱,目前在切换的时候css隔离相对不够彻底。
  2. wujie采用iframe运行时,webcomponent挂载dom的方式,避免了js污染和css污染的问题
  3. 相比于qiankun的多个js沙箱,个人感觉iframe的沙箱更为牢靠。
  4. 用于尝试新的技术,才能有所收获。

最后

微前端没有银弹 各有各的优点,选择适合自己项目的框架即可,反正都要做一些适配。