微前端工程之間的通訊

微前端工程之間的通訊

原理

  • 使用發佈訂閱者模式:一方訂閱,一方發佈。
  • 使用單例模式:一個工程內使用同一個實例。
  • 微前端加載,首先加載父工程,隨後根據配置加載對應的一個或者多個子工程。
  • 父工程先一步生成實例,並傳遞給子工程。子工程則使用這個實例初始化自己的實例。 共享同一個實例。

代碼

import { getState } from "./utils";

let instance: BusInstance | null = null;

class BusInstance {
  private queue: Function[] = [];

  subscription = (cb: Function) => {
    this.queue.push(cb);
  };

  unsubscription = (cb: Function) => {
    this.queue = this.queue.filter((item) => item !== cb);
  };

  notify = (data: any) => {
    this.queue.forEach((cb) => {
      if ("[object Function]" === Object.prototype.toString.call(cb)) {
        cb(data);
      }
    });
  };
}

export function init(value: BusInstance) {
  // 父工程將自己的 bus 實例,傳遞給子工程
  // 子工程調用 init 函數,這樣父子工程共享同一個實例
  instance = value;
}

export default function Bus() {
  // 使用單例模式
  // 除了第一次返回的都是同一個實例
  if (!instance) {
    instance = new BusInstance();
  }
  return instance;
}

export function subscription(func: Function) {
  const bus = Bus();
  bus.subscription(func);
}

export function unsubscription(func: Function) {
  const bus = Bus();
  bus.unsubscription(func);
}

export function notify(data: { type: string; payload: any }) {
  const bus = Bus();
  // 使用 setTimeout 原因是,防止在 reducer 中調用 notify 函數,導致觸發另外一個 reducers
  setTimeout(() => {
    bus.notify(data);
  });
}

export function destory() {
  instance = null;
}

export function initGlobal(global: any) {
  setTimeout(() => {
    const [state, store] = getState();
    if (store) {
      store.dispatch({ type: "global/initGlobal", payload: global() });
    }
  });
}

使用以 @umijs/plugin-qiankun 爲例

本質上使用 single-spa 用法一致,主要是需要共享實例。

主工程

import Bus from "@static/common/bus";
import { getState, cloneDeep } from "@static/common/utils";

export const qiankun = function () {
  return Promise.resolve({
    apps: window.SUB_APP_CONFIG.map(({ name, entry, base }) => ({
      name,
      entry,
      base,
      props: {
        // 父工程生成實例傳遞給子工程
        bus: Bus(),
        global() {
          const [state] = getState();
          if (state) {
            return cloneDeep(state.global);
          }
          return {};
        },
      },
    })),
    prefetch: true,
    defer: true,
    lifeCycles: {
      beforeLoad() {
        loadingFn(true);
      },
      beforeMount() {
        loadingFn(false);
      },
    },
  });
};
import { Effect, qiankunStart, Reducer } from "umi";
import { getSideOptions, getState } from "@static/common/utils";
import { notify, subscription } from "@static/common/bus";
import globalModel, { GlobalState } from "@static/models/global";

const { namespace, state, reducers, effects, subscriptions } = globalModel;

const newEffects: {
  [index: string]: Effect;
} = {
  ...effects,
  *authUserInfo(anyAction, effectsCommandMap) {
    if (effects) {
      yield effects.authUserInfo(anyAction, effectsCommandMap);
      if (window.singleSpaNavigate) {
        setTimeout(() => {
          qiankunStart();
        });
      }
    }
  },
};

const newReduccers: {
  [index: string]: Reducer<GlobalState | undefined>;
} = {
  ...reducers,
  updateCollapsed(state, { payload }) {
    if (!state) return state;
    state.collapsed = payload;
    // 發佈消息
    notify({ type: "global/updateCollapsed", payload });
    if (payload) {
      state.nav.openKeys = "";
    } else {
      const { openKeys } = getSideOptions(state.nav.data || []);
      state.nav.openKeys = openKeys;
    }
    state.nav = { ...state.nav };
    return { ...state };
  },
};

export default {
  namespace,
  state,
  reducers: newReduccers,
  effects: newEffects,
  subscriptions,
};

子工程

import {
  initGlobal,
  init,
  destory,
  subscription,
  unsubscription,
} from "@static/common/bus";
import { getState } from "@static/common/utils";

function baseFunc(data: any) {
  const [, store] = getState();
  if (store) {
    const { type, payload } = data;
    if ("global/updateCollapsed" === type) {
      store.dispatch({ type, payload });
    }
  }
}

export const qiankun = function () {
  if (window.singleSpaNavigate) {
    return {
      async bootstrap(props: any) {
        // 共享實例
        init(props.bus);
        // 訂閱
        subscription(baseFunc);
      },
      async mount(props: any) {
        initGlobal(props.global);
      },
      async unmount() {
        // 取消訂閱
        unsubscription(baseFunc);
        destory();
      },
    };
  }
  return Promise.resolve();
};

注意:理論上只要共享了實例,可以是父子、兄弟等等之間互相發送消息

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章