需求:
// 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)
})
}
复制代码
实现:
//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>
);
}
}
复制代码