Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

另辟蹊径,addDataListener主子应用消息通讯、数据传递问题的解决方法 #1419

Open
wiskewu opened this issue Oct 18, 2024 · 1 comment

Comments

@wiskewu
Copy link

wiskewu commented Oct 18, 2024

背景

父应用通过setData发送数据到子应用,子应用使用addDataListener在接收相同数据时不触发回调。本质原因官网也有提及,在setData时会对对象第一层结构字段作检测,如果数据相同则不会发送:
image

同类问题参考

在项目的issues中,有很多相关的issue提到数据通信问题,如 #1356 #1389 #1409 等。

父应用向子应用发送消息,或子应用向父应用发送消息,消息总是可达的

基本原理:在micro-app提供的setGlobalData(data)方法中,不涉及iframe间通讯,不涉及数据JSON拷贝解析等,故我们可以往data中传递函数。
实现步骤:

  1. 封装一个简单的Emitter发布订阅工具;
  2. 在父应用中初始化一个全局数据gData,该数据包含一个emitter实例;
  3. 在父应用调用microApp.start()前,调用microApp.setGlobalData(gData)初始化全局数据;
  4. 在子应用中通过window.microApp.getGlobalData()获取emitter实例;
  5. 父应用调用emitter的发布/订阅方法;
  6. 子应用调用emitter的发布/订阅方法。

注意,这个emitter在实现时不同于常见的发布订阅,需对订阅的事件类型(name)作特殊处理,以区分多个不同应用(主应用/子应用)。

数据通信伪代码

父应用:

var emitter = {
// 注册某个应用对某个类型事件的监听
 on(type, listener, appname) {},
// 取消某个应用的所有注册监听
 offApp(appname) {},
// 取消所有应用对于某个类型事件的注册
offType(type) {},
// 取消某个应用对某个事件类型的注册
offApp(appname, type, listener) {},
// 在某个应用上触发某个事件
emit(appname, type, ...args) {},
// 对所有应用触发某个事件
emitType(type, ...args) {},

// 扩展事件类型,以支持区分多个应用
formatEvtName(appname, type) {},
};

// 启动应用
microApp.setGlobalData({ emitter: emitter });
microApp.start();

// 使用
microApp.getGlobalData().emitter.on('sometype', (data) => { console.log('父应用收到消息:', data) }, 'app-parent');
microApp.getGlobalData().emitter.emit('app-child', 'sometype', '来自父应用的消息');

子应用:

// 使用
window.microApp.getGlobalData().emitter.on('sometype', (data) => { console.log('子应用收到消息:', data) }, 'app-child');
window.microApp.getGlobalData().emitter.emit('app-parent', 'sometype', '来自子应用的消息');

内存安全

为确保内存安全,需在子应用的生命周期中适当地清理事件及其回调。

// 父应用
microApp.start({
  lifeCycles: {
    unmount(e, appname) {
      microApp.getGlobalData().emitter.offApp(appname);
    }
  }
});

emitter伪代码

// 此EventEmitter实现了最基本的发布订阅模型
import EventEmitter from 'path/to/emitter'

interface IListener {
  (...args: any[]): any
}
// 封装一个强化版的支持多应用的发布订阅模型
export default class MicroAppEventEmitter {
  private emitter = new EventEmitter()

  /**
   * 注册(应用)对某个类型事件的监听
   * @param type
   * @param listener
   * @param appName
   */
  public on(type: string, listener: IListener, appName?: string): void {
    if (appName) {
      this.emitter.on(this.formatEvtName(appName, type), listener)
    } else {
      this.emitter.on(type, listener)
    }
  }

  /**
   * 取消某个子应用的所有注册事件
   * @param appName
   */
  public offApp(appName: string) {
    const types = this.emitter.getEvtNames()
    types.forEach((t) => {
      const [name] = this.decodeEvtName(t)
      if (name === appName) {
        this.emitter.off(t)
      }
    })
  }

  /**
   * 取消所有应用对于某个事件类型的注册
   * @param type
   */
  public offType(type: string, listener?: IListener) {
    const types = this.emitter.getEvtNames()
    types.forEach((t) => {
      const [, tType] = this.decodeEvtName(t)
      if (type === tType) {
        this.emitter.off(t, listener)
      }
    })
  }

  /**
   * 取消某个应用对某个事件类型的注册
   * @param appName
   * @param type
   * @param listener
   */
  public off(appName: string, type: string, listener?: IListener): void {
    this.emitter.off(this.formatEvtName(appName, type), listener)
  }

  /**
   * 触发某个类型事件(对所有应用生效)
   * @param type
   * @param args
   */
  public emitType(type: string, ...args: unknown[]) {
    const types = this.emitter.getEvtNames()
    types.forEach((t) => {
      const [, tType] = this.decodeEvtName(t)
      if (tType === type) {
        this.emitter.emit(t, ...args)
      }
    })
  }

  /**
   * 触发指定应用的某个类型事件
   * @param appName
   * @param type
   * @param args
   */
  public emit(appName: string, type: string, ...args: unknown[]): void {
    this.emitter.emit(this.formatEvtName(appName, type), ...args)
  }

  public once(appName: string, type: string, listener: IListener): void {
    const l: IListener = (...args) => {
      listener(...args)
      this.off(appName, type, l)
    }
    this.on(type, l, appName)
  }

  public clear(): void {
    this.emitter.clear()
  }

  private formatEvtName(appName: string, type: string) {
    return `${appName}${MicroAppEventEmitter.delimiter}${type}`
  }

  private decodeEvtName(evtName: string): [string | undefined, string] {
    const [appName, type] = evtName.split(MicroAppEventEmitter.delimiter)
    if (!type) {
      return [undefined, appName]
    }
    return [appName, type]
  }

  private static delimiter = '::'
}


// 使用
microApp.setGlobalData({ emitter: new MicroAppEventEmitter() })

扩展

虽然这里说的是emitter发布订阅模型,但大家可以基于此步骤实现自己的setState方法(异步/同步)或其他逻辑。而不需要使用micro-app怪异的addDataListener方法。

@Jennylx
Copy link

Jennylx commented Nov 11, 2024

好好好!这个酷!可惜我看到的太晚了。。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants