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

前言

  • 本篇通过学习dva-immer来实现_handleAction钩子和onError钩子

dva-immer

  • 先安装看用法:
cnpm i dva-immer -S
  • 顺便说一下,dva目前npmjs下的是2.41,2.5和2.6都是beta版本,去发布网址查看。dva-immer版本没对上的话安装可能提示需要的dva版本不对。不过没关系,这个不影响。
  • 我们在reducer里常常会这样修改状态:
  state:{product:[{number:1},{commit:[{id:1},{id:2}]}],number2:4}
  reducers: {
        add(state,action) {
            return {...state,product:[{number:1},{commit:[{id:1},{id:3}]}]}
        }
    },
  • 是不是非常麻烦。我如果就改最后那个,那得还把原来的给弄出来。有了immer就可以方便的修改了。
  • 可以直接state.product[1].commit[1].id=3就行了。比如:
import {produce}from 'immer'
...
  state:{product:[{number:1},{commit:[{id:1},{id:2}]}],number2:4}
  reducers: {
        add(state,action) {
            return produce((state,draftState=>{
            	draftState.product[1].commit[1].id=3
            }))
        }
    },
  • 但这样写还是麻烦,每次都要写produce,如果想直接在里面写修改那句,就需要用dva-immer了。
app.use(require('dva-immer').default())
....
    reducers: {
        add(state, action) {
            state.number += 1
        },
  • 这就可以了,直接把state改掉,相当于在操作draftState。
  • 注意列表添加之类需要push或者shift之类操作方法。
  • 下面就来实现dva-immer,实际用的是_handleActions这个钩子,打印下就看见了。

dva-immer.js

import produce from 'immer'

export default function () {
    return {
        _handleActions(reducers, defaultState) {
            return function (state = defaultState, action) {
                let { type } = action
                let ret = produce(state, draft => {
                    const reducer = reducers[type]
                    if (reducer) {
                        return reducer(draft, action)
                    }
                })
                return ret || {}
            }
        }
    }
}
  • 这个钩子有点像用户配置的中间件,其中reducer(draft,action)可以看出,传给model的reducers里的其实是draft,用户在reducers里写函数实际写的是reducer(draft, action)。
  • 下面在我们的dva中加入它。首先是plugin里加入:
const hooks = [
    "onEffect",//增强effect
    "extraReducers",//添加reducer
    "onAction",
    "onStateChange",
    "onReducer",
    "extraEnhancers",
    "_handleActions"
]
...
    use(plugin) {
        const { hooks } = this
        for (let key in plugin) {
            if (key === 'extraEnhancers') {
                hooks[key] = plugin[key]
            } else if (key === '_handleActions') {
                this._handleActions = plugin[key]
            } else {
                hooks[key].push(plugin[key])
            }
        }//{hook:[fn|obj]}
    }
  • 这里赋值的话是直接给这个实例上属性。这样实例可以通过plugin._handleActions拿到。
  • 还记得当时写dva的index.js里有个getReducer函数,里面当action派发过来会查找对应的reducer,匹配后执行用户逻辑吗?没错,dva的index.js里就把这个钩子加在这里:
    function getReducer(m) {
        let handleActions = plugin._handleActions
        let everyreducers = m.reducers//reducers的配置对象,里面是函数
        let reducer = function (state = m.state, action) {//组织每个模块的reducer
            let reducer = everyreducers[action.type]//相当于以前写的switch
            if (reducer) {
                return reducer(state, action)
            }
            return state
        }
        if (handleActions) {
            return handleActions(everyreducers, m.state)
        } else {
            return reducer
        }
    }
  • 这样这个钩子就完成了。

onError

  • 这个钩子是全局捕获错误的,这个会捕获用户传入effects的逻辑错误和subscription的done错误
...
     onError(e) {
        alert(e)
    }
...
 subscriptions: {
        subscribe({ dispatch, history }, done) {
            //这里可以输入一些监听事件,满足监听条件进行dispatch或者别的操作。
            //就相当于不需要手动派发,而是监听派发
            done("错误")
            history.listen((location) => {
                console.log(location.pathname);
                document.title = location.pathname
                //这里操作dom会有顺序问题,这里比react渲染dom先调用,所以react的渲染会覆盖这里操作。
            })
        }
    }
  • 实现的话一样,加入钩子:
const hooks = [
    "onEffect",//增强effect
    "extraReducers",//添加reducer
    "onAction",
    "onStateChange",
    "onReducer",
    "extraEnhancers",
    "_handleActions",
    "onError"
]
  • 然后需要在effects和subscription里添加error的回调:
function getWatcher(key, effect, model, onEffect, onError) {
    function put(action) {
        return sagaEffects.put({ ...action, type: prefixType(action.type, model) })
    }
    return function* () {
        yield sagaEffects.takeEvery(key, function* (action) {//对action进行监控,调用下面这个saga
            if (onEffect) {
                for (const fn of onEffect) {//oneffect是数组
                    effect = fn(effect, { ...sagaEffects, put }, model, key)//这里只是存起来,多个effect就递归
                }
            }
            try {
                yield effect(action, { ...sagaEffects, put })//真正的用户effect执行逻辑
            } catch (e) {
                onError.forEach(fn => fn(e))//报错了传给它就行
            }

        })
    }
}
    function runSubscription(m) {
        for (let key in m.subscriptions) {
            let subscription = m.subscriptions[key]
            subscription({ history, dispatch: app._store.dispatch }, error => {//加个参数就行了,这里就是done
                let onError = plugin.get('onError')
                onError.forEach(fn => fn(error))
            })
        }
    }
  • 这样就完成了。试试在effects里throw个错误或者subscriptions里调用done走不走onError。
  • dva内容差不多就这么多了。
发布了163 篇原创文章 · 获赞 9 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/104129703
dva
今日推荐