# 组件/插件间通信

组件不是孤岛,不同功能的组件、插件可以在其关键时机对外发布消息,运营者可配置该消息触发其他组件的某些行为。

# 发布-订阅

考虑到多框架技术栈的组件以及纯 JS 脚本的插件之间通信,很容易想到发布-订阅

// A 抽奖组件,对外提供抽奖 API
function draw() {
  console.log('3,抽奖>>>>');
};
events.on('draw', function(){
  console.log('2,收到消息,准备抽奖>>>>');
  draw();
});

其他组件触发调用抽奖方法只需通过发布抽奖消息。

// B 按钮组件,发布 draw 消息
function onClick() {
  console.log('1,发布抽奖消息>>>>');
  events.emit('draw');
};

纯粹的发布-订阅后果是:凡是有通信往来的组件集体耦合。试想,如果B 按钮组件想触发分享组件分享,是不是分享组件继续 订阅draw消息?而如果留资表单组件提交完也想触发分享,是不是也得发布draw消息?这太扯了!

注意,组件化场景的通信特色也即基本要求是:需要让可视化配置的数据介入进来。

# Raptor 通信实现

下图展示了组件 A组件 B的通信过程

通信

event 中心接收组件的通信配置,为此包装 emit 方法传入组件,该过程中组件以自订阅、自发布方式,订阅的 handle 方法中借助 ref 组件 集合中心,找到通信的【被调方】。

之所以用自订阅、自发布方式,目的是打通与自定义代码的连接。

# 通信代码示例

以 React 按钮组件为例,组件点击时 emit 消息,也对外提供设置按钮文案的方案。

需要注意的是,通信的主调方只需要 emit,被调方只需保证方法能被外界访问到,React 函数组件需要使用 useImperativeHandle

// 截取部分代码
function SingleButton(props) {
  const {
    emit,
    raptorLang
  } = props;
  useImperativeHandle(ref, () => ({
    setButtonText
  }));

  const clickHandle = () => {
    if (disabled) return;
    emit('click');
  }

  return (
    <div className="single-button" style={style}>
      <a
        className="primary-button"
        disabled={disabled}
        onClick={clickHandle}
        style={getStyle()}
      >
        {buttonText || raptorLang.buttonText}
      </a>
    </div>
  )
}

# 缺点分析

Raptor 组件通信方案于组件开发来说,主调方 emit,被调方只需保证其方法能被外界调用,看似被调方简单,实际上方案耦合了框架的 ref 机制, 且对于开发来讲,该通信方案不易理解。

# 更好的设计

删掉和框架耦合的 ref API,通信的被调方改为使用订阅风格 on(key, fn) (背后要合成 key),主调方背后的 handle 方法查本次 emit(key) 的通信配置,找到被调方组件 id, 继续 emit(合成key)。

通信

相比于 Raptor 现在的实现,本通信方案更加简单、美观、易理解。

组件开发者在编码风格上维持易于理解的发布-订阅。

// A 抽奖组件,对外提供抽奖 API
function draw() {
  console.log('3,抽奖>>>>');
};
// 1,on 是包装后的,其根据该组件的 hashId 重新订阅 ${hashId}-draw 消息。
events.on('draw', function(){
  console.log('2,收到消息,准备抽奖>>>>');
  draw();
});
// B 按钮组件,发布 draw 消息。
function onClick() {
  console.log('1,发布抽奖消息>>>>');
  // 1,emit 是包装后的,其根据该组件 'draw' 的通信配置,找到被调方信息。
  // 2,发布 ${hashId}-draw 消息。
  events.emit('draw');
};
上次更新: 5/16/2022, 1:42:01 AM