【问题标题】:Simulating a command queue and undo stack with RxJS使用 RxJS 模拟命令队列和撤消堆栈
【发布时间】:2015-03-23 14:56:32
【问题描述】:

我正在尝试使用 RxJS 复制 this demo。该演示是一个小型应用程序,用户在其中控制机器人。机器人可以向前或向后移动,向左或向右旋转,以及捡起或放下物品。用户可以对命令进行排队(例如“Forward”、“rotate”),当用户点击“Execute”按钮时,队列中的命令就会被执行。用户还可以撤消已经执行的命令

传统上,这个应用程序很容易使用尚未执行的命令队列来实现。执行的命令被压入堆栈,每当按下撤消按钮时,顶部的命令就会弹出并撤消。

我可以通过这样做“收集”命令并执行它们:

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)

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

invokes.subscribe(function(command_array){...})

或者我可以创建一个 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){ ...});

老实说,我不知道哪种方法会更好,但我又一次不知道这是否对我来说太相关了。我一直在试图弄清楚如何实现撤消功能,但我完全不知道该怎么做。

在我看来,它必须是这样的(对不起格式):

-c1---c2-c3--------->

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

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

所以我的问题是双重的:

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

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

【问题讨论】:

标签: javascript system.reactive reactive-programming rxjs


【解决方案1】:

您必须继续维护撤消堆栈的状态。我认为您收集命令的方法是合理的。如果您保留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 */ });

编辑:仅使用没有可变数组的 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) { ... });

【讨论】:

  • 感谢您的回答!有什么方法可以(几乎)纯粹使用 Rx 功能来做到这一点?意思是,我宁愿不使用这样的数组。我忘了提到这一点,但这个演示是针对我的硕士论文的,我正在比较应用程序是如何使用变革风格和反应风格实现的;因此,即使不方便,我也不得不坚持使用流。
  • 我添加了代码来做它纯粹的反应。也许有更好的方法?我想不出来。
  • 非常感谢!你是对的,它看起来确实很复杂,哦,好吧。我不完全确定我是否理解所有这些,但我会在明天进行测试并报告。再次感谢!
  • 成功了!惊人的。不过有一些修复:scan() 中的第一个返回应该有“q: ACC.q.slice(1)”;为了模拟堆栈,第二个返回应该有“q: [value].concat(acc.q)”。我会将您的答案标记为已接受,但如果您能解决这些问题,那么我相信它对其他人会更有帮助。再次感谢!
【解决方案2】:

我刚刚创建了类似的东西,虽然有点复杂。 也许它可以帮助某人。

  // Observable for all keys
  const keypresses = Rx.Observable
    .fromEvent(document, 'keydown')

  // Undo key combination was pressed
  //  mapped to function that undoes the last accumulation of pressed keys
  const undoPressed = keypresses
    .filter(event => event.metaKey && event.key === 'z')
    .map(() => (acc) => acc.slice(0, isEmpty(last(acc)) && -2 || -1).concat([[]]))

  // a 'simple' key was pressed
  const inputChars = keypresses
    .filter(event => !event.altKey && !event.metaKey && !event.ctrlKey)
    .map(get('key'))
    .filter(key => key.length === 1)

  // the user input, respecting undo
  const input = inputChars
    .map((char) => (acc) =>
      acc.slice(0, -1).concat(
        acc.slice(-1).pop().concat(char)
      )
    ) // map input keys to functions that append them to the current list
    .merge(undoPressed)
    .merge(
      inputChars
        .auditTime(1000)
        .map(() => (acc) => isEmpty(last(acc)) && acc || acc.concat([[]]))
    ) // creates functions, that start a new accumulator 1 sec after the first key of a stroke was pressed
    .scan(
      (acc, f) => f(acc),
      [[]],
    ) // applies the merged functions to a list of accumulator strings
    .map(join('')) // join string
    .distinctUntilChanged() // ignore audit event, because it doesn't affect the current string

【讨论】:

    猜你喜欢
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    • 2013-09-18
    • 2021-04-05
    • 2021-06-04
    • 2021-04-29
    • 1970-01-01
    • 2010-11-20
    相关资源
    最近更新 更多