在 React Redux 中实现撤消状态更改(撤消 store/history 实现)的最佳方法是什么

What is the best way to implement undo state change (undo store/history implementation) in React Redux

我在 redux react 应用程序中寻找 历史重做/存储重置为先前状态

我发现 blog 告诉它可以通过将当前、未来和过去的状态存储在堆栈中并相应地重置来完成。

我也在 Whosebug 中找到了类似的 question,但它没有给我正确的答案,或者我可能很难理解。

我在这里构建了一个演示 ToDo app and have used redux-logger to log store details with previous state and updated state. You can find the code

我们在 redux 中是否有 store reset 方法,以便我们可以获取以前的状态并更新具有当前、过去和未来状态的 store 的 store?

What is the best way ...

最好的方法总是很难定义,这实际上取决于您的用例和要求,包括客户端和服务器。

但要开始,您可以考虑使用库或查看他们如何解决这个问题,例如:

https://github.com/omniscientjs/immstruct

https://www.npmjs.com/package/redux-undo

https://github.com/PowToon/redux-undo-redo

带有 redux 待办事项示例的教程 undo/redo: https://github.com/reactjs/redux/tree/master/examples/todos-with-undo

或者您可以自己实现,作为 redux,您可以存储所有应用程序状态。一个简单的堆栈可能是一种在任何给定时间存储您的应用程序状态的简单而有效的方法。

let yourHistory = [state1, state2, state3];

我创建了状态 undo/redo 快照管理器 class,这对于跟踪 HTML 元素的更改历史非常有用。

  <div id="buttons">
     <button type="button" id="undo_btn">Undo</button>
     <button type="button" id="redo_btn">Redo</button>
  </div>
  <br/><br/>
  <div id="content">
     <label>
        Input1:
        <input type="text" value="" />
     </label>
     <br/><br/>
     <label>
        Input2:
        <input type="text" value="" />
     </label>
     <br/><br/>
     <label>
        Input3:
        <input type="text" value="" />
     </label>
     <br/><br/>
     <label>
        Input4:
        <input type="text" value="" />
     </label>
     <br/><br/>
  </div>

  <script type="text/javascript">
  var StateUndoRedo = function() {
     var init = function(opts) {
        var self = this;
        self.opts = opts;
        if(typeof(self.opts['undo_disabled']) == 'undefined') {
           self.opts['undo_disabled'] = function() {};
        }
        if(typeof(self.opts['undo_enabled']) == 'undefined') {
           self.opts['undo_enabled'] = function() {};
        }
        if(typeof(self.opts['redo_disabled']) == 'undefined') {
           self.opts['redo_disabled'] = function() {};
        }
        if(typeof(self.opts['redo_enabled']) == 'undefined') {
           self.opts['redo_enabled'] = function() {};
        }
        if(typeof(self.opts['restore']) == 'undefined') {
           self.opts['restore'] = function() {};
        }
        self.opts['undo_disabled']();
        self.opts['redo_disabled']();
     }

     var add = function(state) {
        var self = this;
        if(typeof(self.states) == 'undefined') {
           self.states = [];
        }
        if(typeof(self.state_index) == 'undefined') {
           self.state_index = -1;
        }
        self.state_index++;
        self.states[self.state_index] = state;
        self.states.length = self.state_index + 1;
        if(self.state_index > 0) {
           self.opts['undo_enabled']();
        }
        self.opts['redo_disabled']();
     }

     var undo = function() {
        var self = this;
        if(self.state_index > 0) {
           self.state_index--;
           if(self.state_index == 0) {
              self.opts['undo_disabled']();
           } else {
              self.opts['undo_enabled']();
           }
           self.opts['redo_enabled']();

           self.opts['restore'](self.states[self.state_index]);
       }
     }

     var redo = function() {
        var self = this;
        if(self.state_index < self.states.length) {
           self.state_index++;
           if(self.state_index == self.states.length - 1) {
              self.opts['redo_disabled']();
           } else {
              self.opts['redo_enabled']();
           }
           self.opts['undo_enabled']();

           self.opts['restore'](self.states[self.state_index]);
       }
     }

     var restore = function() {
        var self = this;
        self.opts['restore'](self.states[self.state_index]);
     }

     var clear = function() {
        var self = this;
        self.state_index = 0;
        //self.states = [];
     }

     return {
        init: init,
        add: add,
        undo: undo,
        redo: redo,
        restore: restore,
        clear: clear
     };
  };

  //initialize object
  var o = new StateUndoRedo();
  o.init({
     'undo_disabled': function() {
        //make the undo button hidden
        document.getElementById("undo_btn").disabled = true;
     },
     'undo_enabled': function() {
        //make the undo button visible
        document.getElementById("undo_btn").disabled = false;
     },
     'redo_disabled': function() {
        //make the redo button hidden
        document.getElementById("redo_btn").disabled = true;
     },
     'redo_enabled': function() {
        //make the redo button visible
        document.getElementById("redo_btn").disabled = false;
     },
     'restore': function(state) {
        //replace the current content with the restored state content
        document.getElementById("content").innerHTML = state;
     }
  });

  //initialize first state
  o.add(document.getElementById("content").innerHTML);
  o.restore();
  o.clear();

  //bind click events for undo/redo buttons
  document.getElementById("undo_btn").addEventListener("click", function() {
     o.undo();
  });
  document.getElementById("redo_btn").addEventListener("click", function() {
     o.redo();
  });

  //bind change events for content element
  document.getElementById('content').addEventListener("change", function(event) {
     // the following is required since vanilla JS innerHTML 
     // does not capture user-changed values of inputs
     // so we set the attributes explicitly (use jQuery to avoid this)
     var elems = document.querySelectorAll("#content input");
     for(var i = 0; i < elems.length; i++) {
        elems[i].setAttribute("value", elems[i].value);
     }

     //take a snapshot of the current state of the content element
     o.add(document.getElementById("content").innerHTML);
  });
  </script>

查看这个 JSFiddle:https://jsfiddle.net/up73q4t0/56/

对于在 2020 年寻找解决方案的任何人。您不必将整个状态对象存储为现在、过去和未来。

相反,您可以只存储有关已更改内容的详细信息。这可以使用 ImmerJS 来实现。它记录对状态对象所做的所有更改并生成称为补丁的东西。

示例: 如果 age32 更新为 40,则生成的补丁将是:

补丁: [ { op: 'replace', path: [ 'age' ], value: 40 } ]

反向补丁: [ { op: 'replace', path: [ 'age' ], value: 32 } ]

它还公开了一种将这些 patches/inverse 补丁应用到状态的方法 - applyPatch。因此,要撤消,我们可以应用一个反向补丁,要重做,我们可以应用一个补丁。

您可以在此处找到完整实施的详细信息:Implementing Undo-Redo Functionality in Redux using Immer