redux-saga
effects
call(fn, ...args)
创建一个 Effect 描述信息,用来命令 middleware 以参数 args 调用函数 fn。
call([context, fn], ...args)
类似 call(fn, ...args),但支持传递 this 上下文给 fn,在调用对象方法时很有用。
apply(context, fn, [args])
call([context, fn], ...args) 的另一种写法。
cps(fn, ...args)
创建一个 Effect 描述信息,用来命令 middleware 以 Node 风格的函数(传入callback的函数)的方式调用 fn。
take(actionType)
takeEvery是一个高级语法糖,内部是靠take实现的,takeEvery是一个死循环,会一直监听,他不会阻塞saga执行。
export default function* watchChange() {
while (true) {
let action = yield take('*');
let state = select();
console.log('after state', state, action);
}
}
take只会监听一次,出发一次就销毁,返回action。
take('*')
代表通配符。
export default function* watchAsyncIncrement() {
for (let i = 0; i < 3; i++) {
const action = yield take(TYPES.ASYNC_INCREMENT);
console.log(action);
yield put({ type: TYPES.INCREMENT });
}
alert('最多只能点三次!');
}
fork(fn,...args)
相当于开启了一个新的进程,fork不会阻塞当前的saga。而take会阻塞。fork返回一个任务标识,可用cancel销毁。
cancel(task)
取消任务fork返回的任务执行。
cancelled
cancelled(),如果当前任务被cancel取消返回true。
put(action)
向store派发一个action,返回action。
race({start,stop})
function * recorder(){
yield race({
start:call(start),
stop:take(types.STOP),
})
}
redux-saga原理
index.tsx
import EventEmiter from 'events';
let times = (fn, time) => () => --time === 0 ? fn() : times.bind(null, fn, time);
interface SagaMiddleware {
(api: any): (next: any) => (action: any) => void,
run: (rootSaga: any) => void
}
export default function createSagaMiddleware() {
//@ts-ignore
let sagaMiddleware: SagaMiddleware = function (api) {
let { dispatch, getState } = api;
let events = new EventEmiter();
const run = function (gen, callback?: any) {
let it = typeof gen == 'function' ? gen() : gen;
let next = (val?: any) => {
let { value: effects, done } = it.next(val);
if (!done) {
if (effects instanceof Promise) {
effects.then(next);
} else if (typeof effects[Symbol.iterator] === 'function') {
run(effects);
next();
} else {
switch (effects.type) {
case 'TAKE':
events.once(effects.actionType, next);
break;
case 'PUT':
dispatch(effects.action);
next();
break;
case 'CALL':
{
let { fn, context, args } = effects.payload;
let promise = fn.apply(context, args);
promise.then(next);
break;
}
case 'FORK':
let _it = effects.gen();
run(_it);
next(_it);
break;
case 'CANCEL':
effects.task.return();
next();
break;
case 'CPS':
let { fn, args } = effects.payload;
fn(...args, (err, val) => {
if (err) {
throw new Error(err);
}
next(val);
})
break;
case 'ALL':
let fns = effects.fns;
let len = fns.length;
let cb = times(next, len);
fns.forEach(fn => run(fn, cb));
break;
}
}
} else {
callback && callback();
}
}
next();
}
sagaMiddleware.run = run;
return function (next) {
return function (action) {
events.emit(action.type, action);
next(action);
}
}
}
return sagaMiddleware;
}
effects.tsx
export function take(actionType) {
return {
type: 'TAKE',
actionType
}
}
export function put(action) {
return {
type: 'PUT',
action
}
}
export function delay(ms, val?: any) {
let delayP = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(val);
}, ms);
});
}
return call(delayP, ms, val);
}
export function call(fn, ...args) {
let context = null;
if (Array.isArray(fn)) {
[context, fn] = fn;
}
return {
type: 'CALL',
payload: {
fn,
context,
args
}
}
}
export function cps(fn, ...args) {
return {
type: 'CPS',
payload: {
fn,
args
}
}
}
export function fork(gen) {
return {
type: 'FORK',
gen
}
}
export function cancel(task) {
return {
type: 'CANCEL',
task
}
}
export function* takeEvery(actionType, task) {
yield fork(function* () {
while (true) {
yield take(actionType);
yield task();
}
})
}
export function all(fns) {
return {
type: 'ALL',
fns
}
}