使用RxJS模拟命令队列和撤消堆栈

T.K*_*nta 5 javascript reactive-programming system.reactive rxjs

我正在尝试使用RxJS 复制此演示.该演示是一个小应用程序,用户控制机器人.机器人可以向前或向后移动,向左或向右旋转,以及拾取或放下物品.用户可以对命令进行排队(例如"前进","旋转"),并且当用户单击"执行"按钮时,将执行队列中的命令.用户还可以撤消已执行的命令.

传统上,使用尚未执行的命令的队列实现此应用程序将非常容易.执行的命令被压入堆栈,每当按下撤销按钮时,顶部命令将被弹出并撤消.

我能够"收集"命令并通过执行以下操作来执行它们:

var id = 0;
var add = Rx.Observable.fromEvent($("#add"), 'click').map(function(){
  var ret = "Command_"+id;
  id++;
  return ret
})
var invoke = Rx.Observable.fromEvent($("#invoke"), 'click')
var invokes = add.buffer(invoke)
Run Code Online (Sandbox Code Playgroud)

buffer()方法将流转换为数组流.我可以订阅调用流并获取命令数组:

invokes.subscribe(function(command_array){...})
Run Code Online (Sandbox Code Playgroud)

或者我可以创建一个Rx.Subject(),我只是逐个推送命令:

var invoked_commands = new Rx.Subject()
invokes.subscribe(function(command_array){
  for(var i=0; i < command_array.length; i++){
    invoked_commands.onNext(command_array[i])
  }
});

invoked_commands.subscribe(function(command){ ...});
Run Code Online (Sandbox Code Playgroud)

说实话,我不知道哪种方法会更好,但我再一次不知道这对我来说是否过于相关.我一直试图弄清楚如何实现撤消功能,但我完全不知道如何去做.

在我看来,它必须是这样的(抱歉格式化):

-c1 --- C2-C3 --------->

---------------- u --- u->("你"=点击撤消按钮)

---------------- c3 - c2>(从最新到最旧的命令,调用undo()方法)

所以我的问题是双重的:

  1. 我收集命令的方法好吗?
  2. 如何实现撤消功能?

编辑:我正在比较变形和反应风格,我正在使用两者来实现这个演示.因此,我想尽可能坚持使用Rx*功能.

Bra*_*don 4

您必须继续维护撤消堆栈的状态。我认为你收集命令的方法是合理的。如果您保留您的Subject内容,则可以通过对该主题进行另一个订阅来将撤消功能与命令执行分离:

var undoQueue = [];
invoked_commands.subscribe(function (c) { undoQueue.unshift(c); });
Rx.Observable
    .fromEvent($("#undo"), "click")
    .map(function () { return undoQueue.pop(); })
    .filter(function (command) { return command !== undefined; })
    .subscribe(function (command) { /* undo command */ });
Run Code Online (Sandbox Code Playgroud)

编辑:仅使用 Rx 而不使用可变数组。这看起来不必要地复杂,但是哦,它很实用。我们用来scan维护撤消队列,并发出一个包含当前队列以及是否应该执行撤消命令的元组。我们将执行的命令与撤消事件合并。执行命令添加到队列中,撤消事件从队列中弹出。

var undo = Rx.Observable
    .fromEvent($("#undo"), "click")
    .map(function () { return "undo"; });
invoked_commands
    .merge(undo)
    .scan({ undoCommand: undefined, q: [] }, function (acc, value) {
        if (value === "undo") {
            return { undoCommand: acc.q[0], q: acc.q.slice(1) };
        }

        return { undoCommand: undefined, q: [value].concat(acc.q) };
     })
     .pluck("undoCommand")
     .filter(function (c) { return c !== undefined })
     .subscribe(function (undoCommand) { ... });
Run Code Online (Sandbox Code Playgroud)