Redux 官方实现撤销重做 -- 解析

Redux 官方实现撤销重做 – 解析

官方的地址:https://www.redux.org.cn/docs/recipes/ImplementingUndoHistory.html

官方提供了一个提供可撤销功能的 reducer enhancer 的库,官方也有提简易实现的教程。以下是我个人总结,即便大家读了之后,对实现可撤销重做原理有初步的理解,启发下思维。

01 数据结构设计

State 是单一数据源,是 redux 管理数据地方,数据结构设计如下:

1
2
3
4
5
{
past: Array<T>,
present: T,
future: Array<T>
}
  • past 用于存放“过去”历史记录数组
  • present 用于存放当前数据
  • future 用于存放发生撤退时,“未来”历史记录的数组

太干,可能听着一头雾水,例如,这边以一个计数器为例(初始为 0,点击一次++1)

1.点击一次

1
2
3
4
5
6
// 将 {count:0 } 作为历史记录推入 past 中,将最新的值 {count:1} 放到 present
{
past: [{count:0}],
present: {count:1},
future: []
}

2.再点击一次

1
2
3
4
5
6
// 将 {count:1 } 作为历史记录推入 past 中,将最新的值 {count:2} 放到 present
{
past: [{count:0},{count:1}],
present: {count:2},
future: []
}

3.发生一次撤退

1
2
3
4
5
6
// 发生一次撤退,将 {count:1 } 作为历史记录推出 past,放入 present,并将原来 present {count:2} 推入 future 中
{
past: [{count:0}],
present: {count:1},
future: [{count:2}]
}

4.发生一次重做

1
2
3
4
5
6
// 发生一次重做,将 {count:2} 推出 future,放入最新值 present 上,并将原来 present {count:1} 推回 past 中
{
past: [{count:0},{count:1}],
present: {count:2},
future: []
}

相信大家看到这里,已经有所了解,设计这个数据结构的初衷了。是以一种快照式记录所有历史数据,就像是给你每次操作的所有数据拍个照,记录成历史记录存放到数组中

02 撤退重做算法

处理 Undo

  • 移除 past 中的最后一个元素。
  • 将上一步移除的元素赋予 present
  • 将原来的 present 插入到 future最前面

处理 Redo

  • 移除 future 中的第一个元素。
  • 将上一步移除的元素赋予 present
  • 将原来的 present 追加到 past最后面

处理其他 Action

  • 将当前的 present 追加到 past最后面
  • 将处理完 action 所产生的新的 state 赋予 present
  • 清空 future

03 高阶 reducer (Reducer Enhancers)

reducer enhancer(或者 higher order reducer),高阶 reducer 和 react 的高阶组件非常类似,它是通过接收一个 reducer 并生成返回一个新的 reducer ,使得新的 reducer 拥有新的特性。举一个例子

很想是我们 IDE 插件,IDE 可以编写代码,装了代码提示插件之后,有了更好代码书写体验,但是当插件卸载了也并不影响原来写代码的功能。下图就是使用高阶 reducer 前后区别图。

image-20220513180319247

04 编写 Reducer Enhancer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function undoable(reducer) {
// 以一个空的 action 调用 reducer 来产生初始的 state
const initialState = {
past: [],
present: reducer(undefined, {}),
future: [],
}

// 返回一个可以执行撤销和重做的新的reducer
return function (state = initialState, action) {
const { past, present, future } = state

switch (action.type) {
case 'UNDO':
const previous = past[past.length - 1]
const newPast = past.slice(0, past.length - 1)
return {
past: newPast,
present: previous,
future: [present, ...future],
}
case 'REDO':
const next = future[0]
const newFuture = future.slice(1)
return {
past: [...past, present],
present: next,
future: newFuture,
}
default:
// 将其他 action 委托给原始的 reducer 处理
const newPresent = reducer(present, action)
if (present === newPresent) {
return state
}
return {
past: [...past, present],
present: newPresent,
future: [],
}
}
}
}

05 官方插件

Redux Undo 是一个库能帮助我们实现可撤销功能。

1
npm install --save redux-undo

详细请见,官方 github