【dva】dva使用与实现(五)

前言

  • 接上篇,这篇实现onReducer钩子。

利用onReducer实现redux-undo

  • 记得前面实现了个钩子叫extraReducers,extra顾名思义是额外的reducer,自己添加的新的处理逻辑。而这个onReducer是增强reducer,类似于reducer中间件的感觉。并不是增加额外的reducer。
  • 先看一个插件叫redux-undo。这个插件可以让状态有时间旅行的功能。听起来很酷对吧,实际上就是你派发的action改变了状态,然后它就把之前的状态记录下,目前的状态记录下。当你回退操作的时候,目前的状态就拿之前的状态,未来的状态就变为前面目前的状态。实际工作中,这玩意能用在啥地方真不太容易能想到,不过对于经常要重做或者撤销操作倒是很友好。但是需要注意,如果结合别的一些使用可能效果不是太好,比如dva-loading,因为dvaloading在状态改变前后都派发动作,所以使用撤销或者重做点一次只能撤销一个dispatch,就是dvaloading的start或者hide,点第二次才能真正撤销异步的那次操作,点第三次还得再次撤销start或者hide。
  • 首先,看官方用法,安装这玩意:
cnpm i redux-undo -S
  • 然后引入它:
import undoable, { ActionCreators } from 'redux-undo'
let app = dva({
    onReducer: reducer => {
        let undoReducer = undoable(reducer)
        return function (state, action) {
            let newState = undoReducer(state, action)
            let router = newState.present.router ? newState.present.router : newState.present.routing
            return { ...newState, router: router }
        }
    }
})
  • 这个undoable类似于中间件,所以对传来的reducer进行包裹,返回一个增强过的reducer。为啥不直接返回undoReducer?因为前面使用路由时,获取路由信息是在router上,现在没有router了,于是获取不到。由于这个增强器把state改成past,present,future了,所以真正的router会在present里,于是我们可以改写时把这个router拎出来,这样就能取到了。以前版本叫routing,现在叫router。
  • 然后增加2按钮,重做和撤销。
  <button onClick={() => { props.dispatch(ActionCreators.undo()) }}>撤销</button>
  <button onClick={() => { props.dispatch(ActionCreators.redo()) }}>重做</button>
  • 另外connect也需要修改,因为connect的给组件提供的状态是present的。
let ConnectedCounter1 = connect(state => state.present.counter1)(Counter1)
  • 这样就可以使用了。
  • 下面试着自己写个redux-undo。原理就是上面说的保存状态做成现在过去未来,除了现在不是数组,过去和未来都是个数组,就像有个指针一样,在这些里面切换。从刚才配置里看见这个是使用了onReducer的钩子,然后传入的是原版reducer,另外还需要做个action。
const UNDO = 'UNDO'
const REDO = 'REDO'
export default function (reducer) {
    let initialState = {
        past: [],
        present: reducer(undefined, {}),
        future: []
    }
    return function (state = initialState, action) {
        let { past, present, future } = state
        switch (action.type) {
            case UNDO: //撤销,把当前放未来,过去里拿出来放现在
                if (past.length !== 0) {
                    if (!future) future = []
                    return {
                        present: past.pop(),
                        past,
                        future: [present, ...future]
                    }
                } else {
                    return {
                        past, present, future
                    }
                }
            case REDO: //重做 把未来放到当前,把现在放过去
                if (future.length !== 0) {
                    if (!past) past = []
                    return {
                        past: [...past, present],
                        present: future.shift(),
                        future
                    }
                } else {
                    return {
                        past, present, future
                    }
                }
            default://默认就是正常操作
                const newState = reducer(present, action)
                if (!past) past = []
                return {
                    past: [...past, present],
                    present: newState,
                    future
                }
        }
    }
}
export const ActionCreators = {
    undo() {
        return { type: UNDO }
    },
    redo() {
        return { type: REDO }
    }
}
  • 注意这个initialState里present的值reducer的返回值,如果用原版的话是不能不传的,使用dva2.6以下也会跟dva2.6不太一样,所以用自己写的就没问题。它内部对reducer传入参数进行了判断和报错。
  • 然后就是内部加个钩子吧:

plugin.js

const hooks = [
    "onEffect",//增强effect
    "extraReducers",//添加reducer
    "onAction",
    "onStateChange",
    "onReducer"
]


export function filterHooks(options) {
    return Object.keys(options).reduce((prev, next) => {
        if (hooks.indexOf(next) > -1) {
            prev[next] = options[next]
        }
        return prev
    }, {})
}
export default class Plugin {
    constructor() {
        this.hooks = hooks.reduce((prev, next) => {
            prev[next] = []
            return prev
        }, {})//{hook:[],hook:[]}
    }
    use(plugin) {
        const { hooks } = this
        for (let key in plugin) {
            hooks[key].push(plugin[key])
        }//{hook:[fn|obj]}
    }
    get(key) {//合并值
        if (key === 'extraReducers') {
            return Object.assign({}, ...this.hooks[key])
        } if (key === 'onReducer') {
            return getOnReducer(this.hooks[key])
        } else {
            return this.hooks[key]
        }
    }
}
function getOnReducer(hook){
    return function(reducer){
        for (const reducerEnhancer of hook ){
            reducer=reducerEnhancer(reducer)
        }
        return reducer
    }
}
  • 对获取到的reducer增强器遍历,然后把原本reducer传进去,最后生成一个新的reducer。
  • 另外dva的index.js就把reducer套一层:
    function createReducer() {
        let extraReducers = plugin.get('extraReducers')
        let reducerEnhancer=plugin.get('onReducer')
        return reducerEnhancer( combineReducers({
            ...initialReducers,
            ...extraReducers//这里是传来的中间件对象
        }))//reducer结构{reducer1:fn,reducer2:fn}
    }
  • 为什么套到createReducer里去?别忘了app的start执行前会走一遍,后面如果有替换reducer,还会走一遍这个,所以在方法里改一遍就可以复用了。
  • 这样就实现了。剩下的下次写。
发布了163 篇原创文章 · 获赞 9 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/104102170
dva