Redux 中間件方式實現 dva功能

在線的實現地址react

需求:編程

// counter.js
// 擁有 dva model 基本編程規則,不考慮使用錯誤狀況,如 namespace:'home/abc'...
export default {
    namespace: 'counter',
    state: {count:0},
    effects: {
        // 可以使用 Generator,傳入參數與 dva 基本一致
    	*set({payload, callback}, {call, put,select}) {
            // 實現 put 方法,用法與dva 基本一致,可調用本 model 或 其餘 namespace model 的 reducers 方法
            // 實現 select 方法,用法與 dva 基本一致
            // 實現 call 方法,用法與 dva 基本一致,須要支持異步(只考慮 Promise 狀況)
             /** 支持語法: let count = yield call(count,payload,1000) let { count, someName } = yield call({ count: call(count, payload, 1000), someName: call(someFunc, payload) }) let [count,someName] = yield call([ call(count,payload,1000), call(someFunc,payload) ]) */
            const state = yield select((state)=> state)
            const data = yield call(count,payload,1000)
            yield put('setProps',data)
            callback && callback(state)
        }
    },
    reducers: {
        // 與 dva 一致,執行和會觸發 redux 中的 dispatch(中間件的 next 方法)
    	setProps(state,action){
    		return {...state,...action.payload}
    	}
    }
}

function count(data,delay = 0){
    return new Promise((resolve,reject)=>{
        return setTimeout(_=>{
            resolve(data)
        },delay)
    })
}

複製代碼

實現:redux

//myDva.js
function isGenerator(obj) {
    return 'function' === typeof obj.next && 'function' === typeof obj.throw
}
function isPromise(obj){
    return obj instanceof Promise || 'function' === typeof obj.then
}
function isObject(value){
    return ({}).toString.call(value) === '[object Object]'
}
// 讓 yield 對應爲異步方法時(返回 Promise),執行完才執行下一行
// 這裏實現了不須要 call 方法也能夠執行異步方法 
function promiseQueue(call,promises){
    let type = Array.isArray(promises) ? 'array': isPromise(promises) ? 'promise': isObject(promises) ? 'object': void 0
    // 不支持 Set Map...
    if(!type) return
    if(type === 'promise') promises = [promises]
    let keys = Object.keys(promises)
    promises = Object.values(promises)
    promises && Promise.all(promises).then(
        onFulfilled.bind(this, call, 'reslove', type, keys),
        onFulfilled.bind(this, call, 'reject')
    ).catch(onFulfilled.bind(this, call, 'reject'))
}

// Promise.all 註冊的函數,來觸發 next 行爲
function onFulfilled( call, fulfill, type, keys, res ) {
    if (!isGenerator(call)) return
    /** 默認: let [count,someName] = yield call([ call(count,payload,1000), call(someFunc,payload) ]) */
    if(type === 'promise') {
        //實現 let count = yield call(count,payload,1000)
        res = res && res[0]
    }else if(type === 'object'){
        /** * 實現 const { count,someName} = yield call({ count: call(count, payload, 1000), someName: call(someFunc, payload) }) */
        let rel = {}
        keys.forEach((k,i)=> rel[k] = res[i])
        res = rel
    }
    let next = {}
    if (fulfill === 'reslove') {
        next = call.next(res)
    } else {
        // Promise.all reject 時
        next = call.throw(res)
    }
    if (!next.done) {
        taskGenerator(call,next.value)
    }
}

/** * 對嵌套的 Generator 與 Promise 能夠正常支持 * @param {*} call Generator 函數 * @param {*} value */
function taskGenerator(call,value) {
    if (isGenerator(call)) {
        let isEffect = null
        let next = {}
        if(value) next.value = value
        while (!isEffect) {
            if (isGenerator(next)) {
                taskGenerator(next.value)
            } else if (next.value && (isPromise(next.value) || typeof next.value === 'object')) {
                // 可能有 Promise 時
                isEffect = null
                promiseQueue(call, next.value)
                break
            }else{
                // 執行 Generator 對應 yield 的表達式或函數
                // 將上一次的 值放入 
                next = call.next(next.value)
            }
            // next.done = true 中止執行 next
            isEffect = next.done
        }
    }
}

function dva() {
    // dvaModels 存儲 model
    let dvaModels = {}
    /** * 返回 { namespace: function (){//Reducer 方法} } * @param {Array} models */
    function createReducers(models) {
        models.forEach(model => {
            // 已 model.namespace 爲 key 的形式賦予 dvaModels 中
            dvaModels[model.namespace] = model
        })
        let reducerKeys = Object.keys(dvaModels)
        let reducer = {}
         // 生成 redux 的 combineReducers 使用的 reducer
        reducerKeys.forEach(key => {
            reducer[key] = (state, action, _key = key) => {
                let thatAtcion = dvaModels[_key]
                state = state || thatAtcion.state
                if (action.type === _key) {
                    return action.data
                }
                return state
            }
        })
        return reducer
    }
    /** * 觸發 model對應 type 的 reducers 方法,並觸發 中間件的 next(dispatch) 方法 * put('setProps':type,data:payload) * @param {*} target * @param {*} next * @param {*} type * @param {*} payload */
    function _put(target, next, type, payload) {
        let args = type.split('/')
        let reducerKey = args[0]
        if (args.length === 2) {
            target = args[0]
            reducerKey = args[1]
        }
        let reducer = dvaModels[target].reducers[reducerKey]
        if (typeof reducer === 'function') {
            let state = dvaModels[target].state
            dvaModels[target].state = reducer(state, {type,payload})
            next({
                type: target,
                data: dvaModels[target].state
            })
        }
    }
    /** * 能夠觸發異步函數,其實已是實現了不用 call 也能夠使用異步 * yield call(delay:fnDescriptor,arg1,arg2) * @param {*} fnDescriptor * @param {...any} args */
    function _call(fnDescriptor, ...args) {
        if(typeof fnDescriptor === 'function'){
            return fnDescriptor(...args)
        }else if(fnDescriptor){
            return fnDescriptor
        }
    }
    /** * callback 參數爲全部 model 中的 state * const state = yield select((state)=> state) * @param {*} callback */
    function _select(callback){
        if( typeof callback !== 'function') return
        let reducerKeys = Object.keys(dvaModels)
        let state ={}
        reducerKeys.forEach(item =>{
            state[item] = dvaModels[item].state
        })
       return callback(state)
    }
    /** * store.dispatch({ * type:'counter/set', * payload:{counter:++counter} * callback:()=>{} * }) * @param {*} param0 * @param {*} next */
    function _dispatch({type = '',payload = {},callback}, next) {
        let args = type.split('/')
        let target = dvaModels[args[0]]
        let effect = target.effects[args[1]]
        let Effect = effect && effect({payload,callback},{
            // args[0] = namespace
            // next = store.dispatch
            put: _put.bind(this, args[0], next),
            call: _call,
            select:_select
        })
        // 異步方法(使用Promise)在利用 Generator 函數實現同步(串行)執行
        Effect && taskGenerator(Effect)
    }

    const middleware = store => next => action => {
        _dispatch(action, next)
    }
    return {
        // 將 model 轉成 redux reducer 形式
        createReducers,
        // dva 中間件
        middleware
    }
}
export default dva()
複製代碼

使用方法:數組

import React, { Component } from 'react';
import {createStore, combineReducers,applyMiddleware} from 'redux'
import {Provider} from './react-redux'
import myDva from './myDva'
import counter from './counter'

// myDva.createReducers([counter]) 以數組的方式註冊 model
const CombineReducers = combineReducers(myDva.createReducers([counter]))
// 註冊 myDva 中間件
const middleware = applyMiddleware(myDva.middleware)
const store = createStore(CombineReducers,middleware)

/** 使用: App.js // ... store.dispatch({ type:'counter/set', payload:{counter:++counter} callback:(state)=>{} }) // ... */

class App extends Component {
  render() {
    return (
        <Provider store={store} > <App /> </Provider>
    );
  }
}

複製代碼
相關文章
相關標籤/搜索