redux-saga 是什么?
家喻户晓,redux-saga 是一个中间件。所谓的中间件就是给 redux 提供额定性能的,简而言之,也就是对 redux 中的 dispatch,加上一些性能,进行包装。
咱们为什么要应用 redux-saga 呢?
在 redux 中,如果咱们 dispatch 一个 action 之后,会调用 reducer 函数。
function dispatch(action) {state = reducer(state, action);
listeners.forEach(l => l());
return action;
}
而后 reducer 是一个纯函数,没有副作用,当咱们须要进行异步申请的时候,这个时候 redux 就不够用了,因而咱们须要借助于 redux-saga。
redux-saga 如何进行裁减 redux 的性能呢?
中间件的根本格局
咱们在裁减中间件的时候会应用一个 applyMiddleware 的函数,对原有的 store.dispatch 进行裁减。
function applyMiddleare(...middlewares) {return function (createStore) {return function (reducer, preloadedState) {let store = createStore(reducer, preloadedState);
let dispatch;
let middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action),
};
let chain = middlewares.map((middleware) => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch,
};
};
};
}
//compose 函数是:function compose(funcs){return funcs.map((a, b) => (...args) => a(b(...args)))
}
由上面这段代码能够看出一个中间件,是承受一个 middleAPI 也就是 store,以及上一次革新后的 dispatch,而后返回一个新的革新后的 dispatch。
let chain = middlewares.map((middleware) => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
所以一个中间件的格局应该是:
function middleware(store) {return (lastDispatch) => {return (action) => {
// 进行新的革新
// 比方 logger 中间件是加上 consolelog 而后 lastDispatch
// 比方 redux-thunk 是如果 action 是一个函数的话应该如何解决
};
};
}
redux-saga 基本原理:
saga 采纳 generator 函数来 yield effect,generator 函数的作用是能够暂停执行,下次再从上次暂停的中央继续执行。
redux-saga 根本根本应用和具体实现:
v redux-saga 中 saga 分类:
- rootsaga,也就是 saga 的入口
- 监听 saga,也就是 watchsaga,个别通过 yieldtake 来监听
-
具体执行的 saga,通过 put fork cps 等等来执行
// 执行 saga function * workerSaga(){yield put({type:actionTypes.ADD}); } // 监听 saga function * watcherSaga(){yield take(actionTypes.ADD_SYNC); yield workerSaga();} // 入口 saga export default function* rootSaga() {yield watcherSaga(); }
咱们晓得在应用 redux-saga 的时候咱们应用 createSagaMiddleware 来进行创立一个 saga 函数中间件,而后在 saga 函数增加了一个 run 属性,而后在调用了 applyMiddleware 之后执行 saga.run(rootsaga)。saga.run 的时候咱们就把入口 saga 放到了 redux-saga 的逻辑程序外面进行执行了。当初咱们想一下这个过程应该怎么去实现:
首先,createSagaMiddleware 就是创立 saga 的中间件,依照下面我所说的中间件的格局,所以 createSagaMiddleware 应该这样写
function createSagaMiddleware() {function sagaMiddleware({ getState, dispatch}) {return function (lastDispatch) {return function (action) {const result = lastDispatch(action);
return result;
}
}
}
return sagaMiddleware;
}
而后须要给 sagaMiddleware 增加 run 函数,在 run 函数中咱们执行了一个 runsaga 的函数。这是一个相似于 co 的库,递归调用 generator 函数,直到 it.next() 返回的 done 值是 false 的时候才会完结。在这期间会对不同的 effect 的 type 进行不同的解决因而实现了 effect 的异步执行。
sagaMiddleware.run= function (...args) {runSaga(...args)}
export default function runSaga(env, saga) {let { channel, dispatch} = env;
let it = typeof saga === 'function'?saga():saga; // yield fork 的时候间接是执行了一遍 runsaga()// 所以 saga 可能就不是一个 function
function next(value) {let {value:effect,done} = it.next(value);
if (!done) {if(typeof effect[Symbol.iterator] === 'function'){// 这种是 yield 前面接了一个 saga 的状况,如 yield watchSaga()
runSaga(env,effect);
next();// 不会阻止以后 saga 持续向后走}else if (effect instanceof Promise) {// 如果 yield 了一个 new promise
effect.then(next);
}else{switch (effect.type) {
case effectTypes.TAKE:
// ...
case effectTypes.PUT:
// ...
break;
default:
break;
}
}
}
}
next();}
通过 effect.type 对不同的类型进行函数的解决。
take: 监听一次
用法:
yield take(actiontype)
监听一次,而后持续向下执行。
case effectTypes.TAKE:
channel.once(effect.actionType,next);
其中 channel 是一个相似于 node EventEmitter 的公布订阅模型,咱们能够实现一个。
function channel() {let listeners = [];
function once(type, listener) {
listener.type = type;
listener.cancel = function () {listeners = listeners.filter((item) => item !== listener);
};
listeners.push(listener);
}
function put(action) {for (let i = 0; i < listeners.length; i++) {if (listeners[i].type === action.type) {
// 只有一次监听
listeners[i](action);
listeners[i].cancel();}
}
}
return {
once,
put,
};
}
那咱们什么时候去派发函数让 take 去监听呢?没错就是咱们在 enhance dispatch 的时候进行派发动作的,因而咱们 createSagaMiddleware 返回的 dispatch 中退出派发的动作,进而可能让 take 进行监听。
function createSagaMiddleware() {function sagaMiddleware({ getState, dispatch}) {return function (lastDispatch) {return function (action) {const result = lastDispatch(action);
channel.put(action);
return result;
}
}
}
return sagaMiddleware;
}
fork 不会阻塞的 runsaga
用法:yield fork(saga) // 返回一个 task 对象,对象里有 cancel 属性
case effectTypes.FORK:
runSaga(env,effect.saga);
next();
所以就算此时 yield fork 没有执行实现,这个时候也会持续相加执行下一个 yield,因而不会梗塞,这也是和 put 的区别。
takeEvery: 基于 fork 和 take 进行实现。
用法:yield takeEvery(actionType)
原理是每当执行 takeEvery 的时候就会从新开启一个新的过程,新的过程是一个 while true 循环,每个循环都会 take 监听
function takeEvery(type, saga){function *sagaHelper(){while(true){yield take(type)
yield fork(saga)
}
}
fork(sagaHelper)
}
call 能够调用 promise 函数
用法:yield call(promiseFn)
case effectTypes.CALL:
effect.fn(...effect.args).then(next);
break;
cps 回调函数的解决形式
用法:
yield cps(fn, params1, params2)
function fn(param1, param2, callback){callback(null,data)
}
跟 promise 的区别在于应用了 callback 的解决形式,如果呈现 error 的话,那么 next 执行的时候就 it.throw() 抛出异样。
case effectTypes.CPS:
if(err){effect.fn(...effect.args,(err,data)=>{ }else{next(err,true);
next(data);
}
});
break;
all 都执行实现之后才持续向下执行
用法:yield all ([iterator1,iterator2])
case effectTypes.ALL:
const {iterators} = effect;
let result =[];
let count = 0;
iterators.forEach((iterator, index) => {runSaga(env, iterator,(data)=> {result[index] =data;
if(++count===iterators.length){next(result)
}
})
总结:
在 redux-saga 中,yield 了一个 effect 之后,会在 effect.js 中转换成相应的 effecttype,而后通过 runsaga 这个函数来进行不同的转换。runsaga 这个函数相似于 co 原理,递归调用 next 函数,终止条件是 it.next() 返回的 done 是 true 值。