action icon indicating copy to clipboard operation
action copied to clipboard

为什么 `.add()` API 没有采用队列机制?

Open cssmagic opened this issue 10 years ago • 1 comments

为什么 .add() API 没有采用队列机制?

本问题摘自 C4 前端交流会的现场观众提问。

背景

大家最熟悉的 DOM 事件 API .addEventListener() 是队列机制——可以为元素的同一事件多次添加事件处理函数,当事件触发时,这些事件处理函数会被依次调用。

因此,当大家看到 action.add() 这个 API 时,很自然就会发出疑问:它的工作方式是队列式吗?如果不是,为什么?

文档

实际上 .add() API 并没有设计队列机制。对于重复添加同名动作的情况,文档是这样描述的:

如果在定义动作时使用了已经存在的动作名,则相当于用新的动作函数替换原有的动作函数。原因在于 action.add() 方法添加的是 “动作”,不是事件监听器;而每个动作名只能对应一个动作函数。

如果你觉得 .add() 这个接口名容易误解,可以自行创建并使用 .define().register() 这样的别名。

设计

从上述文档可以看出,Action 的设计力求 “简单易用”:

  • 用 “动作” 的概念屏蔽事件、冒泡、监听器等底层实现。
  • 动作采用基本的 “名值对” 模型,“一个萝卜一个坑”。

引入队列机制,会使整概念模型复杂化。作为系统中的一项基础设施,Action 必须做到简单、易理解、易排错,因为当大量上层代码依赖这个基础设施时,稳定性必定是它的首要目标。

同时,对一个通用型类库来说,不加节制地增强功能,并不会令它更加通用;相反,膨胀的体积和大多数人用不到的功能反而会限制它的适用面。一个单纯的机制,恰恰更容易被按需扩展。

扩展

每个类库都有适用场景,Action 的原生功能适用于中小型网站项目;而对于更复杂的场景,Action 也可以通过各种方式在上层被扩展。

在 C4 前端交流会上,有同学认为,由于 .add() 没有队列机制(不可以对同一动作追加多个动作函数),无法满足以下需求:

如果我有一组类似的按钮需要执行一些共同的动作、同时也有自己特有的动作,如何实现?

实际上,我们完全可以在动作函数这个层面来完成业务逻辑的抽象。为便于讨论,我们先约定场景和术语:“Btn A” 和 “Btn B” 都需要执行自己的特有行为 fnAfnB,同时它们也需要执行共同行为 fnC。那么我们可以采用以下一些的扩展方案:

  • 使用 AOP 等方式来给 fnAfnB 分别追加 fnC,得到 fnACfnBC,再把它们作为真正的动作函数传给 .add() 方法。

  • .add() 只管接收动作列表(用对象组织的名值对),至于这个动作列表是怎么来的,它是不关心的。我们完全可以用一套更高级的机制来构造出自己需要的动作列表。假设我们根据自己的业务需要实现了一套 ActionComposer,可以按需构造动作函数:

    var myActions = {
        'action-a': ActionComposer.combine('fnA', 'fnC'),
        'action-b': ActionComposer.combine('fnB', 'fnC')
    }
    action.add(myActions)
    

    只要 myActions 是合法的数据结构,.add() 就能正常使用。

  • 顺着上面的思路进一步拓展,我们甚至可以不用操心动作列表的产生。我们可以预先定一些原子操作(比如这里的 fnAfnBfnC),然后在 HTML 层面通过一定的命名约定来书写动作名。比如:

    <button data-action="a+c">Btn A</button>
    <button data-action="b+c">Btn B</button>
    

    然后使用一个构建工具来扫描 HTML,自动生成以下结构并传递给 action.add()

    {
        'a+c': ActionComposer.combine('fnA', 'fnC'),
        'b+c': ActionComposer.combine('fnB', 'fnC')
    }
    

    这样连定义动作这一步都可以交给程序来自动完成了。

总之,我们的思路就是把 Action 当作一种底层的基础设施来用——上层的业务逻辑可以很复杂,但底层可以很简单!

cssmagic avatar Dec 24 '14 06:12 cssmagic

为避免可能存在的歧义,从 v0.4 开始,action.add() 更名为 action.define()

action.add() 仍可使用,但已不建议。

cssmagic avatar Jan 24 '19 14:01 cssmagic