加入收藏 | 设为首页 | 会员中心 | 我要投稿 汽车网 (https://www.0577qiche.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

Redux 实现撤销历史运用

发布时间:2023-04-07 14:01:20 所属栏目:教程 来源:
导读:在应用中构建撤销和重做功能往往需要开发者刻意地付出一些精力。对于经典的 MVC 框架来说,这不是一个简单的问题,因为你需要克隆所有相关的 model 来追踪每一个历史状态。

此外,你需要考虑整个撤销堆栈,因为用
在应用中构建撤销和重做功能往往需要开发者刻意地付出一些精力。对于经典的 MVC 框架来说,这不是一个简单的问题,因为你需要克隆所有相关的 model 来追踪每一个历史状态。

此外,你需要考虑整个撤销堆栈,因为用户的初始更改也是可撤销的。

这意味着在 MVC 应用中实现撤销和重做功能时,你不得不使用一些类似于 Command 的特殊的数据修改模式来重写你的应用代码。

然而你可以用 Redux 轻而易举地实现撤销历史,因为以下三个原因:

不存在多个模型的问题,你需要关心的只是 state 的子树。

state 是不可变数据,所有修改被描述成独立的 action,而这些 action 与预期的撤销堆栈模型很接近了。

reducer 的签名 (state, action) => state 可以自然地实现 “reducer enhancers” 或者 “higher order reducers”。它们在你为 reducer 添加额外的功能时保持着这个签名。撤销历史就是一个典型的应用场景。

在动手之前,确认你已经阅读过基础教程并且良好掌握了 reducer 合成。本文中的代码会构建于基础教程的示例之上。

Redux 实现撤销历史

理解撤销历史
接下来将讲解关于撤销历史当中的设计状态结构以及设计算法的具体使用方式与示例。

设计状态结构
撤销历史也是应用 state 的一部分,我们没有必要以不同的方式实现它。当你实现撤销和重做这个功能时,无论 state 如何随着时间不断变化,你都需要追踪 state 在不同时刻的历史记录。

例如,一个计数器应用的 state 结构看起来可能是这样:

{
  counter: 10
}
如果我们希望在这样一个应用中实现撤销和重做的话,我们必须保存更多的 state 以解决下面几个问题:

撤销或重做留下了哪些信息?

当前的状态是什么?

撤销堆栈中过去(和未来)的状态是什么?

为此我们对 state 结构做了以下修改以便解决上述问题:

{
  counter: {
    past: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
    present: 10,
    future: []
  }
}
现在,如果按下“撤销”,我们希望恢复到过去的状态:

{
  counter: {
    past: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ],
    present: 9,
    future: [ 10 ]
  }
}
再按一次:

{
  counter: {
    past: [ 0, 1, 2, 3, 4, 5, 6, 7 ],
    present: 8,
    future: [ 9, 10 ]
  }
}
当我们按下“重做”,我们希望往未来的状态移动一步:

{
  counter: {
    past: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ],
    present: 9,
    future: [ 10 ]
  }
}
最终,当处于撤销堆栈中时,用户发起了一个操作(例如,减少计数),那么我们将会丢弃所有未来的信息:

{
  counter: {
    past: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
    present: 8,
    future: []
  }
}
有趣的一点是,我们在撤销堆栈中保存的是数字、字符串、数组或是对象都不重要,因为整个结构始终保持一致:

{
  counter: {
    past: [ 0, 1, 2 ],
    present: 3,
    future: [ 4 ]
  }
}
{
  todos: {
    past: [
      [],
      [ { text: 'Use Redux' } ],
      [ { text: 'Use Redux', complete: true } ]
    ],
    present: [ { text: 'Use Redux', complete: true }, { text: 'Implement Undo' } ],
    future: [
      [ { text: 'Use Redux', complete: true }, { text: 'Implement Undo', complete: true } ]
    ]
  }
}
它看起来通常都是这样:

{
  past: Array<T>,
  present: T,
  future: Array<T>
}
我们可以在顶层保存单一的历史记录:

{
  past: [
    { countera: 1, counterB: 1 },
    { countera: 1, counterB: 0 },
    { countera: 0, counterB: 0 }
  ],
  present: { countera: 2, counterB: 1 },
  future: []
}
也可以分离历史记录,这样我们可以独立地执行撤销和重做操作:

{
  countera: {
    past: [ 1, 0 ],
    present: 2,
    future: []
  },
  counterB: {
    past: [ 0 ],
    present: 1,
    future: []
  }
}
接下来我们将会看到如何合适地分离撤销和重做。

设计算法
无论何种特定的数据类型,重做历史记录的 state 结构始终一致:

{
  past: Array<T>,
  present: T,
  future: Array<T>
}
让我们讨论一下如何通过算法来操作上文所述的 state 结构。我们可以定义两个 action 来操作该 state:UNDO 和 REDO。在 reducer 中,我们希望以如下步骤处理这两个 action:

处理 Undo
移除 past 中的最后一个元素。

将上一步移除的元素赋予 present。

将原来的 present 插入到 future 的最前面。

处理 Redo
移除 future 中的第一个元素。

将上一步移除的元素赋予 present。

将原来的 present 追加到 past 的最后面。

处理其他 Action
将当前的 present 追加到 past 的最后面。

将处理完 action 所产生的新的 state 赋予 present。

清空 future。

(编辑:汽车网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章