redux-undo 的 groupBy 和 filter

本文不是 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

你可能感兴趣的:(redux-undo 的 groupBy 和 filter)