在Redux中实现undo / redo [英] Implementing undo / redo in Redux

查看:390
本文介绍了在Redux中实现undo / redo的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一段时间以来,我一直在讨论如何在 Redux 与服务器交互(通过ajax)。

For a while now I've been wracking my brain as to how you would implement undo / redo in Redux with server interactions (via ajax).

我用命令模式,其中的操作使用执行注册方法作为命令,而不是调度命令的调度操作。然后将命令存储在堆栈中并在需要时引发新操作。

I've come up with a solution using a command pattern where actions are registered with an execute and undo method as Commands, and instead of dispatching actions you dispatch commands. The commands are then stored in a stack and raise new actions where required.

我当前的实现使用中间件来拦截调度,测试命令并调用Command和look的方法类似这样的事情:

My current implementation uses middleware to intercept dispatches, test for Commands and call methods of the Command and looks something like this:

let commands = [];
function undoMiddleware({ dispatch, getState }) {
  return function (next) {
    return function (action) {
      if (action instanceof Command) {
        // Execute the command
        const promise = action.execute(action.value);
        commands.push(action);
        return promise(dispatch, getState);
      } else {
        if (action.type === UNDO) {
            // Call the previous commands undo method
            const command = commands.pop();
            const promise = command.undo(command.value);
            return promise(dispatch, getState);
        } else {
            return next(action);
        }
      }
    };
  };
}



行动



Actions

const UNDO = 'UNDO';
function undo() {
    return {
        type: UNDO
    }
}

function add(value) {
    return (dispatch, getState) => {
        const { counter } = getState();
        const newValue = counter + value;

        return new Promise((resolve, reject) => {
            resolve(newValue); // Ajax call goes here
        }).then((data) => {
            dispatch(receiveUpdate(data));
        });
    }
}

function sub(value) {
    return (dispatch, getState) => {
        const { counter } = getState();
        const newValue = counter - value;

        return new Promise((resolve, reject) => {
            resolve(newValue); // Ajax call goes here
        }).then((data) => {
            dispatch(receiveUpdate(data));
        });
    }
}



命令



Commands

class Command {
  execute() {
    throw new Error('Not Implemented');
  }

  undo() {
    throw new Error('Not Implemented');
  }
}

class AddCommand extends Command {
    constructor(value) {
        super();
        this.value = value;
    }

    execute() {
        return add(this.value);
    }

    undo() {
        return sub(this.value);
    }
}



App



App

const store = createStoreWithMiddleware(appReducer);

store.dispatch(new AddCommand(10)); // counter = 10
store.dispatch(new AddCommand(5)); // counter = 15

// Some time later
store.dispatch(undo()); // counter = 10

(一个更完整的例子其他

我目前使用的方法有几个问题:

There are several issues I've found with my current approach:


  • 由于通过中间件实现,整个应用程序可能只存在一个堆栈。

  • 不能自定义 UNDO 命令类型。

  • 创建一个命令来调用操作,这些操作又会返回promises。

  • 在操作完成之前将命令添加到堆栈中。错误会发生什么?

  • 由于命令未处于状态,因此无法添加is_undoable功能。

  • 您将如何实施乐观更新?

  • Due to implementing via middleware, only one stack may exist for the entire application.
  • Cannot customise UNDO command type.
  • Creating a Command to call actions which in turn return promises seems very convoluted.
  • Commands are added to the stack before the action completes. What happens for errors?
  • As commands are not in state, cannot add is_undoable functionality.
  • How would you implement optimistic updates?

我的问题是,是否有人可以建议更好的方法来实现此功能在Redux中?

My question then, is can anyone suggest a better way of implementing this functionality within Redux?

我现在看到的最大缺陷是在动作完成之前添加的命令,以及如何在混合中添加乐观更新。

The biggest flaws I see right now are the commands being added before actions have completed, and how it would be difficult to add optimistic updates to the mix.

非常感谢任何见解。

推荐答案

进一步辩论@ vladimir-rovensky建议的基于不可变的实现...

不可变对于客户端撤消 - 重做管理非常有效。您可以自己或使用像 immstruct 这样的库来存储不可变状态的最后N个实例。它适合你。由于内置于不可变的实例共享,它不会导致内存开销。

Immutable works very well for client side undo-redo management. You can simply store last "N" instances of the immutable state either yourself or using a library like immstruct which does it for you. It doesn't result in memory overhead due to instance sharing built into immutable.

但是,如果您希望保留每次与服务器同步模型可能会很昂贵这很简单,因为每次在客户端修改时都需要将整个状态发送到服务器。根据状态大小,这将无法很好地扩展。

However, syncing the model every-time with the server may be costly if you wish to keep it simple, because you would need to send the entire state to server every time it is modified on client. Depending on the state size this will not scale well.

更好的方法是仅将修改发送到服务器。当您最初将其发送到客户端时,您需要在您的州中使用修订版标题。对客户端完成的状态的每次其他修改都应该只记录差异并将它们发送到带有修订版的服务器。服务器可以执行diff操作并发送新版本和差异后的状态校验和。客户端可以根据当前状态校验和对此进行验证并存储新版本。差异也可以由标记有修订和校验和的服务器存储在其自己的撤消历史记录中。如果在服务器上需要撤消,则可以反转差异以获得状态,并且可以执行校验和检查。
我遇到的不可变的差异库是 https://github.com/intelie /不可变-JS-DIFF 。它创建了RFC-6902样式的补丁,您可以使用 http://hackersome.com/p/zaim/immpatch执行它们。 服务器状态。

A better approach will be to send only the modifications to the server. You need a "revision" header in your state when you send it initially to the client. Every other modification to the state done on client should record only the diffs and send them over to the server with the revision. The server can execute the diff operations and send back a new revision and checksum of the state following the diffs. The client can verify this against current state checksum and store the new revision. The diffs can also be stored by the server tagged with the revision and checksum in its own undo history. If an undo is desired on the server, the diffs can be reversed to obtain the state and checksum checks can be performed. A diffing library for immutable which I came across is https://github.com/intelie/immutable-js-diff. It creates RFC-6902 style patches which you can execute using http://hackersome.com/p/zaim/immpatch on the server state.

优势 -


  • 简化的客户端架构。服务器同步不会遍布客户端代码。每当客户端状态发生变化时,它都可以从您的商店启动。

  • 简单撤消/重做与服务器的同步。无需单独处理不同的客户端状态更改,也就是没有命令堆栈。差异补丁以一致的方式跟踪几乎任何类型的状态变化。

  • 没有重大事务命中的服务器端撤消历史记录。

  • 验证检查确保数据一致性。

  • 修订标题允许多客户端同时更新。

  • Simplified client architecture. Server sync up is not scattered all over the client code. It can be initiated from your stores whenever client state changes.
  • Simple undo/redo syncs with server. No need to handle different client state changes individually, aka no command stacks. The diff patch tracks almost any kind of state changes in a consistent fashion.
  • Server side undo history without major transaction hits.
  • Validation checks ensure data consistency.
  • Revision header allows for multi-client simultaneous updates.

这篇关于在Redux中实现undo / redo的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆