redux-saga 就是用来处理上述副作用(异步任务)的一个中间件。它是一个接收事件,并可能触发新事件的过程管理者,为你的应用管理复杂的流程。
src/index.js
import React from 'react'
import ReactDOM from 'react-dom';
import Counter from './components/Counter';
import {Provider} from 'react-redux';
import store from './store';
ReactDOM.render(<Provider store={store}>
<Counter/>
</Provider>,document.querySelector('#root'));
src/sagas.js
import {put,take} from 'redux-saga/effects';
import * as types from './store/action-types';
export function* rootSaga() {
for (let i=0;i<3;i++){
yield take(types.INCREMENT_ASYNC);
yield put({type:types.INCREMENT});
}
console.log('已经达到最大值');
}
src/components/Counter.js
import React,{Component} from 'react'
import {connect} from 'react-redux';
import actions from '../store/action';
class Counter extends Component{
render() {
return (
<div>
<p>{this.props.number}</p>
<button onClick={this.props.increment}>+</button>
</div>
)
}
}
export default connect(
state => state,
actions
)(Counter);
src/store/index.js
import {createStore, applyMiddleware} from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
import {rootSaga} from '../saga';
let sagaMiddleware=createSagaMiddleware();
let store=applyMiddleware(sagaMiddleware)(createStore)(reducer);
sagaMiddleware.run(rootSaga);
window.store=store;
export default store;
src/store/actions.js
import * as types from './action-types';
export default {
increment() {
return {type:types.INCREMENT_ASYNC}
}
}
src/store/action-types.js
export const INCREMENT_ASYNC='INCREMENT_ASYNC';
export const INCREMENT='INCREMENT';
rc/store/reducer.js
import * as types from './action-types';
export default function (state={number:0},action) {
switch(action.type){
case types.INCREMENT:
return {number: state.number+1};
default:
return state;
}
}
export default function createSagaMiddleware() {
function createChannel() {
let listener={};
function subscribe(actionType,cb) {
listener[actionType]=cb;
}
function publish(action) {
if (listener[action.type]) {
let temp=listener[action.type];
delete listener[action.type];
temp(action);
}
}
return {subscribe,publish}
}
let channel=createChannel();
function sagaMiddleware({getState,dispatch}) {
function run(generator) {
let it=generator();
function next(action) {
let {value:effect,done} = it.next(action);
if (!done) {
switch (effect.type) {
case 'TAKE':
channel.subscribe(effect.actionType,next);
break;
case 'PUT':
dispatch(effect.action);
next();
break;
default:
}
}
}
next();
}
sagaMiddleware.run=run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
src\redux-saga\effects.js
export function take(actionType) {
return {
type:'take',
actionType
}
}
export function put(action) {
return {
type: 'put',
action
}
}
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。 Iterator 的作用有三个:
原生具备 Iterator 接口的数据结构如下。
var arr = [1,2];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
let obj = {
name:'zhufeng',
age:10,
[Symbol.iterator]() {
const self = this;
let values = Object.values(self);
let index = 0;
return {
next() {
return {value:values[index++],done:index>values.length?true:false};
}
};
}
}
let iterator = obj[Symbol.iterator]();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
import {put,take} from '../redux-saga/effects';
import * as types from './action-types';
+export function* increment() {
+ yield put({type:types.INCREMENT});
+}
export function* rootSaga() {
for (let i=0;i<3;i++){
yield take(types.INCREMENT_ASYNC);
+ yield increment();
}
console.log('已经达到最大值');
}
export default function createSagaMiddleware() {
function createChannel() {
let listener={};
function subscribe(actionType,cb) {
listener[actionType]=cb;
}
function publish(action) {
if (listener[action.type]) {
let temp=listener[action.type];
delete listener[action.type];
temp(action);
}
}
return {subscribe,publish};
}
let channel=createChannel();
function sagaMiddleware({getState,dispatch}) {
function run(generator) {
+ let it= typeof generator == 'function'? generator():generator;
function next(action) {
let {value:effect,done} = it.next(action);
if (!done) {
+ if (typeof effect[Symbol.iterator]=='function') {
+ run(effect);
+ next();
+ } else {
switch (effect.type) {
case 'TAKE':
channel.subscribe(effect.actionType,next);
break;
case 'PUT':
dispatch(effect.action);
next();
break;
default:
}
}
}
}
next();
}
sagaMiddleware.run=run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
const task = yield fork(otherSaga, ...args)
import {put,takeEvery} from '../redux-saga/effects';
import * as types from './action-types';
export function* increment() {
yield put({type:types.INCREMENT});
}
export function* rootSaga() {
+ yield takeEvery(types.INCREMENT_ASYNC,increment);
}
src\redux-saga\effects.js
export function take(actionType) {
return {
type:'TAKE',
actionType
}
}
export function put(action) {
return {
type: 'PUT',
action
}
}
export function fork(task) {
return {
type: 'FORK',
task
}
}
export function* takeEvery(actionType,task) {
yield fork(function* () {
while (true) {
yield take(actionType);
yield task();
}
});
}
switch (effect.type) {
case 'TAKE':
channel.subscribe(effect.actionType,next);
break;
case 'PUT':
dispatch(effect.action);
next();
break;
+ case 'FORK':
+ run(effect.task);
+ next();
+ break;
default:
break;
}
import {put,takeEvery} from '../redux-saga/effects';
import * as types from './action-types';
+const delay=ms => new Promise((resolve,reject) => {
+ setTimeout(() => {
+ resolve();
+ },ms);
+});
export function* increment() {
+ yield delay(1000);
yield put({type:types.INCREMENT});
}
export function* rootSaga() {
yield takeEvery(types.INCREMENT_ASYNC,increment);
}
function next(action) {
let {value:effect,done} = it.next(action);
if (!done) {
if (typeof effect[Symbol.iterator]=='function') {
run(effect);
next();
+ } else if(effect.then){
+ effect.then(next);
+ }else {
switch (effect.type) {
case 'TAKE':
channel.subscribe(effect.actionType,next);
break;
export function* increment() {
//yield delay(1000);
yield call(delay,1000);
yield put({type:types.INCREMENT});
}
export function call(fn,...args) {
return {
type: 'CALL',
fn,
args
}
}
case 'CALL':
effect.fn(...effect.args).then(next);
break;
+import {put,takeEvery,delay} from '../redux-saga/effects';
import * as types from './action-types';
export function* increment() {
+ yield delay(1000);
yield put({type:types.INCREMENT});
}
export function* rootSaga() {
yield takeEvery(types.INCREMENT_ASYNC,increment);
}
const innerDelay=ms => new Promise((resolve,reject) => {
setTimeout(() => {
resolve();
},ms);
});
export function delay(...args) {
return call(innerDelay,...args);
}
import {put,takeEvery,call,cps} from '../redux-saga/effects';
import * as types from './action-types';
+const delay = (ms,callback)=>{
+ setTimeout(() => {
+ callback('ok');
+ },ms);
+}
+export function* increment() {
+ let data = yield cps(delay,1000);
+ console.log(data);
+ yield put({type:types.INCREMENT});
+}
export function* rootSaga() {
yield takeEvery(types.INCREMENT_ASYNC,increment);
}
export function cps(fn,...args) {
return {
type: 'CPS',
fn,
args
}
}
case 'CALL':
effect.fn(...effect.args).then(next);
break;
+ case 'CPS':
+ effect.fn(...effect.args,next);
+ break;
default:
break;
src\store\saga.js
import {put,takeEvery,delay,all} from '../redux-saga/effects';
import * as types from './action-types';
export function* increment() {
yield delay(1000);
yield put({type:types.INCREMENT});
}
export function* incrementWatcher() {
yield takeEvery(types.INCREMENT_ASYNC,increment);
}
export function* logger() {
console.log('action');
}
export function* loggerWatcher() {
yield takeEvery(types.INCREMENT_ASYNC,logger);
}
export function* rootSaga() {
yield all([incrementWatcher(),loggerWatcher()]);
console.log('done');
}
export function all(fns) {
return {
type: 'ALL',
fns
}
}
export default function createSagaMiddleware() {
function createChannel() {
let listener={};
function subscribe(actionType,cb) {
listener[actionType]=cb;
}
function publish(action) {
if (listener[action.type]) {
let temp=listener[action.type];
delete listener[action.type];
temp(action);
}
}
return {subscribe,publish};
}
let channel=createChannel();
+ function times(cb,total) {
+ let count=0;
+ return function () {
+ if (++count === total) {
+ cb();
+ }
+ }
+ }
function sagaMiddleware({getState,dispatch}) {
+ function run(generator,callback) {
let it= typeof generator == 'function'? generator():generator;
function next(action) {
let {value:effect,done} = it.next(action);
if (!done) {
if (typeof effect[Symbol.iterator]=='function') {
run(effect);
next();
} else if(effect.then){
effect.then(next);
}else {
switch (effect.type) {
case 'TAKE':
channel.subscribe(effect.actionType,next);
break;
case 'PUT':
dispatch(effect.action);
next();
break;
case 'FORK':
run(effect.task);
next();
break;
case 'CALL':
effect.fn(...effect.args).then(next);
break;
+ case 'ALL':
+ let fns=effect.fns;
+ let done=times(next,fns.length);
+ for (let i=0;i<fns.length;i++){
+ let fn=fns[i];
+ run(fn,done);
+ }
+ break;
case 'CPS':
effect.fn(...effect.args,next);
break;
default:
break;
}
}
+ } else {
+ callback&&callback();
+ }
}
next();
}
sagaMiddleware.run=run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
src\store\saga.js
import {put,take,delay,all,fork,cancel} from '../redux-saga/effects';
import * as types from './action-types';
export function* increment() {
while(true){
yield delay(1000);
yield put({type:types.INCREMENT});
}
}
export function* incrementWatcher() {
const task = yield fork(increment);
yield take(types.STOP_INCREMENT);
yield cancel(task);
}
export function* rootSaga() {
yield all([incrementWatcher()]);
console.log('done');
}
src\components\Counter.js
import React,{Component} from 'react'
import {connect} from 'react-redux';
import actions from '../store/action';
class Counter extends Component{
render() {
return (
<div>
<p>{this.props.number}</p>
<button onClick={this.props.increment}>+</button>
<button onClick={this.props.stop}>stop</button>
</div>
)
}
}
export default connect(
state => state,
actions
)(Counter);
src\redux-saga\effects.js
export function cancel(task) {
return {
type: 'CANCEL',
task
}
}
src\redux-saga\index.js
function sagaMiddleware({getState,dispatch}) {
function run(generator,callback) {
let it= typeof generator == 'function'? generator():generator;
function next(action) {
let {value:effect,done} = it.next(action);
if (!done) {
if (typeof effect[Symbol.iterator]=='function') {
run(effect);
next();
} else if(effect.then){
effect.then(next);
}else {
switch (effect.type) {
case 'TAKE':
channel.subscribe(effect.actionType,next);
break;
case 'PUT':
dispatch(effect.action);
next();
break;
+ case 'FORK':
+ let newTask = effect.task();
+ run(newTask);
+ next(newTask);
+ break;
case 'CALL':
effect.fn(...effect.args).then(next);
break;
case 'ALL':
let fns=effect.fns;
let done=times(next,fns.length);
for (let i=0;i<fns.length;i++){
let fn=fns[i];
run(fn,done);
}
break;
case 'CPS':
effect.fn(...effect.args,next);
break;
+ case 'CANCEL':
+ effect.task.return('over');
+ break;
default:
break;
}
}
} else {
callback&&callback();
}
}
next();
}
sagaMiddleware.run=run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}