本文不是 redux-undo 的 GetStart,所以在阅读之前最好先了解redux-undo。文章只是简单的介绍笔者使用 redux-undo 时所遇到的问题及解决方法。
什么是redux-undo?
higher order reducer to add undo/redo functionality to redux state containers.
原理
简单来说,redux-undo将reducer 分为:past,present,future。past 和 future 都是数组,分别记录 undo 和 redo,present 就是当前的 reducer。例如使用 redux-undo 前是这样子使用:state.counter,那么使用后需要这样子用:state.counter.present。
详情请参考:
Implementing Undo History
redux-undo
配置 undoableConfig
使用 redux-undo 方法如下:
import undoable from 'redux-undo';
undoable(reducer, undoableConfig)
重点是 undoableConfig 中的 groupBy 和 filter。二者皆为一个函数,可以使用 groupByActionTypes、includeAction、excludeAction,也可以使用自定义的函数。filter还可以使用 combineFilters 将自定义filter 和includeAction、excludeAction组合使用。
groupBy
在使用 redux-undo 时有这么一个场景。比如说一个 input 的状态放在 redux,onChange 时会发 action(UPDATE_INPUT) 更新 store。如果想使用 redux-undo 监听这个 input 的输入记录。
const undoableConfig = {
filter: includeAction([UPDATE_INPUT]}
}
那么此时 input 的每次修改都会记录在 undo past 中,输入一个 "input" 字符串会记录5次,这样子显然是不行的。我希望的是只记录一次,所以就需要 groupBy 来配合了。
import undoable, { groupByActionTypes } from 'redux-undo'
const undoableConfig = {
filter: includeAction([UPDATE_INPUT],
groupBy: groupByActionTypes(UPDATE_INPUT),
}
此时就已经实现了 input 的输入只会记录一次,非常棒。但是此时假设需要在 input value 的长度小于5时只记录一次,大于5以后每次修改都记录呢?这时候 groupByActionTypes 就玩不转了。但是别忘了 groupBy 就是一个函数,groupByActionTypes 只是作者提供的一个通用的方法。要实现刚才的需求就需要我们自定义 groupBy 方法了。
import { get } from 'lodash'
const undoableConfig = {
filter: includeAction([UPDATE_INPUT],
groupBy: (action) => get(action, 'payload.undoableGroupName', null),
}
这样子,在需要 groupBy 的时候传入一个参数 undoableGroupName,相同的 undoableGroupName 连续的 action 就只会记录一步。若 undoableGroupName 值则为 null,连续的 action 则不会被分组。
filter
上文已经简单介绍过 filter,简单来说就是筛选出需要记录的 actionTypes。那 redux-undo 既然已经提供了 includeAction,还有什么可说的呢?来看下面这个案例。假设有一个 ACTION_A 是需要记录的,那应该这样子用:
import undoable, { includeAction } from 'redux-undo'
const undoableConfig = {
filter: includeAction([ACTION_A]
}
很简单就能实现。那若是有这么一个 action,在 someAction 中 ACTION_A 就不需要记录。
export const someAction = (var1, var2) => {
return function (dispatch) {
dispatch(subAction_A_ToReducerA(var1))
dispatch(subAction_B_ToReducerB(var2))
}
}
export const subAction_A_ToReducerA = (var1) => {
return {
type: ACTION_A,
var1: var1
}
}
export const subAction_B_ToReducerB = (var2) => {
return {
type: ACTION_B,
var2: var2
}
}
要怎么实现呢?这时候就需要我们自定义 filter 了。redux-undo 提供了 combineFilters 方法,可以将 includeAction 和 customFilter 结合起来。比如像这样:
import undoable, { includeAction } from 'redux-undo'
const undoableConfig = {
filter: combineFilters(
includeAction([ACTION_A],
customFilter
)
}
const customFilter = (action) => !get(action, 'payload.undoableFiltered', false)
这样,someAction 也需要做一定的修改。someAction 中的 ACTION_A 就会被 filter 掉,不被记录下来。
export const someAction = (var1, var2) => {
return (dispatch) => {
const undoableFiltered = true
dispatch(subAction_A_ToReducerA(var1, undoableFiltered))
dispatch(subAction_B_ToReducerB(var2))
}
}
export const subAction_A_ToReducerA = (var1, undoableFiltered) => {
return {
type: ACTION_A,
undoableFiltered: undoableFiltered,
var1: var1
}
}
以上就是笔者在实践中遇到的特殊情况,幸运的是巧妙的使用 groupBy 和 filter解决了以上问题。
参考:
- redux-undo issues #185
- redux-undo issues #181
- Implementing Undo/Redo with Redux
- Implementing Undo History