Redux 官方实现撤销重做 – 解析
官方的地址:https://www.redux.org.cn/docs/recipes/ImplementingUndoHistory.html
官方提供了一个提供可撤销
功能的 reducer enhancer 的库,官方也有提简易实现的教程。以下是我个人总结,即便大家读了之后,对实现可撤销重做原理有初步的理解,启发下思维。
01 数据结构设计
State 是单一数据源,是 redux 管理数据地方,数据结构设计如下:
| { past: Array<T>, present: T, future: Array<T> }
|
past
用于存放“过去”历史记录数组
present
用于存放当前数据
future
用于存放发生撤退时,“未来”历史记录的数组
太干,可能听着一头雾水,例如,这边以一个计数器为例(初始为 0,点击一次++1)
1.点击一次
| // 将 {count:0 } 作为历史记录推入 past 中,将最新的值 {count:1} 放到 present { past: [{count:0}], present: {count:1}, future: [] }
|
2.再点击一次
| // 将 {count:1 } 作为历史记录推入 past 中,将最新的值 {count:2} 放到 present { past: [{count:0},{count:1}], present: {count:2}, future: [] }
|
3.发生一次撤退
| // 发生一次撤退,将 {count:1 } 作为历史记录推出 past,放入 present,并将原来 present {count:2} 推入 future 中 { past: [{count:0}], present: {count:1}, future: [{count:2}] }
|
4.发生一次重做
| // 发生一次重做,将 {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 前后区别图。
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) { const initialState = { past: [], present: reducer(undefined, {}), future: [], }
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: const newPresent = reducer(present, action) if (present === newPresent) { return state } return { past: [...past, present], present: newPresent, future: [], } } } }
|
05 官方插件
Redux Undo 是一个库能帮助我们实现可撤销
功能。
详细请见,官方 github