简体   繁体   English

在 React Redux 中实现撤消状态更改(撤消存储/历史实现)的最佳方法是什么

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

I'm looking out for a history redo/ store reset to the previous state in a redux react application.我正在寻找在redux react应用程序中将历史重做/存储重置为以前的状态

I've found a blog telling it can be done by storing present, future and past states in a stack and resetting accordingly.我发现一个 博客告诉它可以通过在堆栈中存储现在、未来和过去的状态并相应地重置来完成。

I've also found a similar question in StackOverflow , but it doesn't give me a proper answer or maybe it's difficult for me to understand.我也在StackOverflow 中发现了一个类似的问题,但它没有给我一个正确的答案,或者我可能很难理解。

I've built a demo ToDo app and have used redux-logger to log store details with previous state and updated state.我已经构建了一个演示ToDo 应用程序,并使用redux-logger来记录具有先前状态和更新状态的存储详细信息。 You can find the code here.你可以在这里找到代码

Do we have a store reset method in redux, so that we can get the previous state and update the store other that having a store with the present, the past and future states?我们在 redux 中是否有 store reset 方法,以便我们可以获取以前的状态并更新 store 其他具有现在、过去和未来状态的 store?

What is the best way ...什么是最好的方法 ...

Best way is always difficult to define, it really depends of your use-case and requirements included client and server.最好的方法总是很难定义,它实际上取决于您的用例和要求,包括客户端和服务器。

But to get start you you could consider using a library or looking how they approach this problem, some example:但是要开始,您可以考虑使用库或查看他们如何解决此问题,例如:

https://github.com/omniscientjs/immstruct https://github.com/omniscientjs/immstruct

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

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

Tutorial with example of todo undo/redo in redux: https://github.com/reactjs/redux/tree/master/examples/todos-with-undo redux 中 todo undo/redo 示例教程: https : //github.com/reactjs/redux/tree/master/examples/todos-with-undo

Or you could implement your own, as redux you could store all your application state.或者你可以实现你自己的,作为 redux 你可以存储你所有的应用程序状态。 A simple stack could be a simple and efficient way to store your app state at any given time.一个简单的堆栈可能是一种在任何给定时间存储应用程序状态的简单而有效的方式。

let yourHistory = [state1, state2, state3];

For anyone looking for a solution to this in 2020. You don't have to store the entire state object as the present, past, and future.对于在 2020 年寻求解决方案的任何人。您不必将整个状态对象存储为现在、过去和未来。

Instead, you can just store details about what has changed.相反,您可以只存储有关已更改内容的详细信息。 This can be implemented using ImmerJS .这可以使用ImmerJS来实现。 It records all changes done on the state object and generates something called a patch.它记录对状态对象所做的所有更改并生成称为补丁的东西。

Example: If age is updated from 32 to 40 , then the generated patch will be:示例:如果age32更新为40 ,则生成的补丁将是:

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

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

It also exposes a method to apply these patches/inverse patches to the state - applyPatch .它还公开了一种将这些补丁/反向补丁应用于状态的方法 - applyPatch So, to undo we can apply an inverse patch and to redo we can apply a patch.因此,要撤消我们可以应用反向补丁,而要重做我们可以应用补丁。

You can find details of full implementation here: Implementing Undo-Redo Functionality in Redux using Immer您可以在此处找到完整实现的详细信息: 使用 Immer 在 Redux 中实现撤消-重做功能

I created a state undo/redo snapshot manager class, which would be great for tracking the change history on an HTML element.我创建了一个状态撤消/重做快照管理器类,它非常适合跟踪 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>

See this JSFiddle: https://jsfiddle.net/up73q4t0/56/看到这个 JSFiddle: https ://jsfiddle.net/up73q4t0/56/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM