前言
- 本篇通过学习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内容差不多就这么多了。