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

前言

  • 很多东西看起来复杂,实际弄一遍感觉简单,主要还是自己懒。
  • 接上篇

dva-loading

  • 这个是一个全局性质的loading状态变化。
  • 一般我们写个组件变化之类的都会在父组件上定义个状态,然后满足状态条件就渲染特定的东西。这个loading把这些变成全局可用。不需要每次都写了。
  • 实际是我们在派发一个异步动作前后,这个插件会派发它自己的action,从而更改loading状态。
  • 工作模式是这样:true是正在loading状态,一开始,没有组件派发action,所有的loading都是false,一旦有saga收到了action进行异步处理,那么这个saga所在的namespace就会置为true,全局也会置为true,全局是只要有一个namespace是true,它就是true。等异步执行完,这个namespace就会变成false,全局会看是否所有的都是false,如果是,那就会改成false。基础好的可能已经发现,这个操作简直是为some量身定做的。
  • 通过拿到true和false可以得到加载状态。全局可用可以省下大量代码。
  • 光说可能比较难理解,我特地截了图:
    在这里插入图片描述
  • 可以发现在派发saga动作前后会派发个show和hide的action,有个global的true和false,每个组件的true和false,这个saga的true和false。
  • 其实我最佩服的是能想出这模式的想象力。居然还能搞出全局loading,我以前从来没这么想过。
  • 下面看使用方法:
cnpm i dva-loading --save
import createLoading from 'dva-loading'
app.use(createLoading())
  • 三句话解决,完美。

dva-loading实现

  • 但是这个是怎么实现呢?需要借助钩子。
  • 我们打印下createLoading的执行结果,发现就是个这样的对象:
{
extraReducers:{loading:fn}
onEffect:fn
}
  • 这个extraReducers和onEffect就是钩子,dva里做了一些钩子函数,使用use或者给dva传配置都是借助钩子来更改配置,这个和大多数框架差不多。
  • 前面我们实现的dva是没有钩子函数的。所以需要做几个钩子出来。
  • dva使用钩子的方式有2种,一种就是use,一种是直接传配置项,根据上面执行结果发现,这2种其实是一个方法。
  • 所以我们需要提取配置项的钩子,存到dva实例里,并且把use的结果也存进去。
  • 先模仿dva-loading把reducer和effects实现下,利用extraReducer钩子可以制作reducer,我们只需要配置进入combineReducer的reducer即可。
  • 而onEffect是就可以相当于我们自己写effects的中间件,可以拿到要执行的effects。这样我们在执行前后去put我们自己的action,用reducer处理成对应的状态就可以了。
const SHOW = '@@DVA_LOADING/SHOW'
const HIDE = '@@DVA_LOADING/HIDE'
const NAMESPACE = 'loading'
export default function createLoading(options) {
    let initalState = {
        global: false,//全局
        model: {},//用来确定每个namespace是true还是false
        effects: {}//用来收集每个namespace下的effects是true还是false
    }
    const extraReducers = {//这里直接把写进combineReducer的reducer准备好,键名loading
        [NAMESPACE](state = initalState, { type, payload }) {
            let { namespace, actionType } = payload || {}
            switch (type) {
                case SHOW:
                    return {
                        ...state,
                        global: true,
                        model: {
                            ...state.model, [namespace]: true
                        },
                        effects: {
                            ...state.effects, [actionType]: true
                        }
                    }
                case HIDE: {
                    let effects = { ...state.effects, [actionType]: false }//这里state被show都改成true了
                    let model = {//然后需要检查model的effects是不是都是true
                        ...state.model,
                        [namespace]: Object.keys(effects).some(actionType => {//查找修改完的effects
                            let _namespace = actionType.split('/')[0]//把前缀取出
                            if (_namespace != namespace) {//如果不是当前model的effects就继续
                                return false
                            }//用some只要有一个true就会返回,是false就继续
                            return effects[actionType]//否则就返回这个effects的true或者false
                        })
                    }
                    let global = Object.keys(model).some(namespace => {//只要有一个namespace是true那就返回
                        return model[namespace]
                    })
                    return {
                        effects,
                        model,
                        global
                    }
                }
                default: return state
            }
        }
    }
    function onEffect(effects, { put }, model, actionType) {//actiontype就是带前缀的saga名
        const { namespace } = model
        return function* (...args) {
            try {//这里加上try,防止本身的effects执行挂了,然后就一直不会hide,导致整个功能失效。
                yield put({ type: SHOW, payload: { namespace, actionType } })
                yield effects(...args)
            } finally {
                yield put({ type: HIDE, payload: { namespace, actionType } })
            }
        }
    }
    return {
        onEffect,
        extraReducers
    }
}
  • 注释全部写上了,下面我们就需要把这个钩子添加进我们的dva里。

  • 现在情况是我们这个配置是通过use或者options放进dva里生成dva实例,因为我们传来的是个对象,里面有可能有很多东西,所以还要对这个对象进行筛选,提取出我们想要的。于是就可以建一个类,来管理外部对象。

plugin.js

const hooks = [
    "onEffect",//effect中间件
    "extraReducers"//添加reducer
]
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) {//因为会多次use,所以就把函数或者对象push进对应的钩子里
        const { hooks } = this
        for (let key in plugin) {
            hooks[key].push(plugin[key])
        }//{hook:[fn|obj]}
    }
    get(key) {//不同的钩子进行不同处理
        if (key === 'extraReducers') {//处理reducer,就把所有对象并成总对象,这里只能是对象形式才能满足后面并入combine的操作。
            return Object.assign({}, ...this.hooks[key])
        } else {
            return this.hooks[key]//其他钩子就返回用户配置的函数或对象
        }
    }
}
  • 有了个这个钩子管理机,我们还需要在对应的地方插入钩子。
  • 首先是解决use和配置项应该是同一个的问题:
    let plugin = new Plugin()
    plugin.use(filterHooks(opts))
    app.use = plugin.use.bind(plugin)
  • 这样用use实际调用的就是plugin的use。要么进配置项也是会走plugin.use的。
  • 另外我们对配置项进行了过滤,把钩子过滤出来传进use里。
  • 再来就是配置reducer,extraReducer的添加很容易想到,它就是在combine那里多加了一个我们配置好的reducer。
function getReducer(app) {
     let reducers = {
         router: connectRouter(app._history)
     }
     for (let m of app._models) {//m是每个model的配置
         reducers[m.namespace] = function (state = m.state, action) {//组织每个模块的reducer
             let everyreducers = m.reducers//reducers的配置对象,里面是函数
             let reducer = everyreducers[action.type]//相当于以前写的switch
             if (reducer) {
                 return reducer(state, action)
             }
             return state
         }
     }
     let extraReducers = plugin.get('extraReducers')
     return combineReducers({
         ...reducers,
         ...extraReducers//这里是传来的中间件对象
     })//reducer结构{reducer1:fn,reducer2:fn}
 }
  • 注意这个getReducer的函数必须放到dva里面才能拿到plugin。
  • 实际改动就2句话,get,然后放进去即可。
  • 而effects中间件,肯定是对里面执行saga的地方下手了:
function getSagas(app) {
      let sagas = []
      for (let m of app._models) {
          sagas.push(function* () {
              for (const key in m.effects) {//key就是每个函数名
                  const watcher = getWatcher(key, m.effects[key], m, plugin.get('onEffect'))
                  yield sagaEffects.fork(watcher) //用fork不会阻塞
              }
          })
      }
      return sagas
  }
function getWatcher(key, effect, model, onEffect) {
    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)
                }
            }
            yield effect(action, { ...sagaEffects, put })
        })
    }
}
  • 监听watcher的时候对每一个要执行的workerSaga包裹起来,传递过去。
  • 这样dva-loading的所有功能都完成了。
发布了163 篇原创文章 · 获赞 9 · 访问量 3万+

猜你喜欢

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