微前端工程之间的通讯

微前端工程之间的通讯

原理

  • 使用发布订阅者模式:一方订阅,一方发布。
  • 使用单例模式:一个工程内使用同一个实例。
  • 微前端加载,首先加载父工程,随后根据配置加载对应的一个或者多个子工程。
  • 父工程先一步生成实例,并传递给子工程。子工程则使用这个实例初始化自己的实例。 共享同一个实例。

代码

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();
};

注意:理论上只要共享了实例,可以是父子、兄弟等等之间互相发送消息

你可能感兴趣的:(前端知识)