了解 AMD:如何处理具有单向模块关系的响应流

Understanding AMD: how to handle response flow with a one-way module relationship

考虑一下,如果您愿意的话,可以考虑一个具有一些独特性的应用程序 views/states - 让我们称它为游戏。你有一个世界画面、一个战斗画面、一个多人游戏界面,也许还有一两个小游戏。

为了争论,每个视图之间没有很多共同的代码,所以它很适合 AMD - 一个中央 controller/dispatcher,每个游戏状态分成一个单独的 file/view.

dispatcher.core.js
>   overworld.view.js
>   battle.view.js
>   tournament.view.js
>   minigame.view.js

输入和键命令被路由到调度程序,并向下渗透到当前活动视图,后者又根据需要操纵 DOM。单向 AMD 关系,到目前为止一切顺利。

我被挂断的是响应流。通过系统的 API 响应数据是多种多样的,通常会同时影响多个视图。考虑这种情况:

  1. 用户按下按钮移动
  2. 键命令被路由到地图视图以进行移动动画
  3. 地图向服务器发送AJAX请求移动结果
  4. AJAX returns "battle commence" 响应调度员
  5. Dispatcher 告诉 map view 禁用它自己,然后 battle view 初始化

调度程序就是为此而设计的 - 接收指令和分发。这似乎是一个显而易见的选择,而不是让视图直接影响彼此。

但是,这里存在一个根本性缺陷 - 一旦 AJAX 结果从视图发送到调度程序,调度程序与视图之间的单向关系就被破坏了。您可以将调度程序用于 AJAX 回调,或者您可以指示调度程序为您进行 AJAX 调用 - 但无论哪种方式,视图都需要一种引用调度程序的方法,据我所知它,违背了AMD的核心宗旨。对于我的一生,我无法弄清楚如何正确实施!

我的问题是 - 如何正确实施这样的结构?这是 AMD 的局限性,还是我误解了它在更深层次上的用途?


此问题旨在针对更多一般情况,但如果它完全影响答案,我将分别对 AMD 和 AJAX 使用 Require 和 jQuery。

Is this a limitation of AMD, or am I misunderstanding it's use on a deeper level?

AMD 绝不会 强加 一般 对象实例之间的单向关系。 强烈建议避免的情况(因为即使这不是绝对要求)是循环依赖关系 模块之间 。对 AMD 重要的依赖类型是 loading dependencies.

你当然可以有一个名为 dispatcher 的模块:

define(function () {
    function Dispatcher(views) {
        this.views = views;
        for (var ix = 0, view; (view = views[ix]); ++ix)
            view.init(this);
    }

    return Dispatcher;
});

viewAviewB,它们的结构如下:

define(function () {
    function View() {
        // ...
    }

    View.prototype.init = function (dispatcher) {
        this.dispatcher = dispatcher;
    };

    // Etc...

    return View;
});

你的主模块可以做:

define(['dispatcher', 'viewA', 'viewB'], function (Dispatcher, ViewA, ViewB) {

    var viewA = new ViewA();
    var viewB = new ViewB();

    var dispatcher = new Dispatcher([viewA, viewB]);
});   

以上只是一个可能的示意图示例,并不是好的设计的处方。无论如何,关键是就 AMD 而言,在对象之间进行循环引用是完全可行的。

这里没有关于 AMD 的限制;这完全是关于模块本身的设计。


处理此问题的常用方法是使用 event-emitter

dispatcher 可以直接在 view 上调用方法,但是 view 发出 dispatcher 可以监听和响应的事件,从而不需要循环引用(因为 view 不关心事件的去向,所以它不需要对 dispatcher 的引用。)

适合您的示例工作流程,它可能看起来像这样:

  1. overworld 跟踪按键
  2. overworld 响应按键的动画
  3. overworlddispatcher

    发出 'move' 事件
    // overworld.view
    this.emit('move', {data});
    
    // dispatcher
    overworld.on('move', getMoveResult) // getMoveResult fires AJAX request
    
  4. 响应告诉dispatcher战斗时间到了
  5. dispatcher 更新浏览量

    overworld.hide()
    battle.show()