在線的實現地址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>
);
}
}
複製代碼