redux-saga 就是用来处理上述副作用(异步任务)的一个中间件。它是一个接收事件,并可能触发新事件的过程管理者,为你的应用管理复杂的流程。
import reducer from './reducer';
import {createStore,applyMiddleware,compose} from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from '../sagas';
let sagaMiddelware = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
let store = composeEnhancers(applyMiddleware(sagaMiddelware))(createStore)(reducer);
sagaMiddelware.run(rootSaga);
export default store;
import { put,takeEvery,all } from 'redux-saga/effects';
//import {delay} from 'redux-saga';
import * as types from './store/action-types';
import 'babel-polyfill'
//用于返回一个延迟 1 秒再 resolve 的 Promise
export const delay = ms => new Promise(resolve=>setTimeout(resolve,ms));
//我们的工作saga,将异步执行increment任务
export function* incrementAsync(){
console.log('incrementAsync');
//Sagas 被实现为 Generator 函数,它 yield 对象到 redux-saga middleware。 被 yield 的对象都是一类指令,指令可被 middleware 解释执行
//当 middleware 取得一个 yield 后的 Promise,middleware 会暂停 Saga,直到 Promise 完成。
yield delay(1000);
//一旦 Promise 被 resolve,middleware 会恢复 Saga 去执行下一个语句(更准确地说是执行下面所有的语句,直到下一个 yield)
// 调用 put意味着Saga 指示 middleware 发起一个 INCREMENT 的 action
yield put({type:types.INCREMENT});
}
//我们监听saga,在每一个INCREMENT_ASYNC派生后,派发一个新的incrementAsync任务
export function* watchIncrementAsync(){
yield takeEvery(types.INCREMENT_ASYNC,incrementAsync);
}
export function* hello(){
console.log('hello');
}
export default function* rootState(){
yield all([watchIncrementAsync()]);
}
这个 Saga 将监听所有发起到 store 的 action,然后将它们记录到控制台
function * logger(getState,action){
console.log('action',action);
console.log('newState',getState());
}
function* watchAndLog(getState){
yield takeEvery('*',logger,getState);
}
function* watchAndLog2(getState,action){
while(true){
//take 就像我们更早之前看到的 call 和 put。它创建另一个命令对象,告诉 middleware 等待一个特定的 action
//正如在 call Effect 的情况中,middleware 会暂停 Generator,直到返回的 Promise 被 resolve
//在 take 的情况中,它将会暂停 Generator 直到一个匹配的 action 被发起了
//在 takeEvery 的情况中,被调用的任务无法控制何时被调用, 它们将在每次 action 被匹配时一遍又一遍地被调用。并且它们也无法控制何时停止监听。
//而在 take 的情况中,控制恰恰相反。与 action 被 推向(pushed) 任务处理函数不同,Saga 是自己主动 拉取(pulling) action 的。
const action = yield take('*');
console.log('action',action);
console.log('newState',getState());
}
}
export function* watchIncrementAsync(){
yield takeEvery(types.INCREMENT_ASYNC,incrementAsync);
}
export function* watchIncrementAsync2(getState){
for(let i=0;i<3;i++){
const action = yield take(types.INCREMENT_ASYNC,incrementAsync);
}
yield put({type:types.MAX_NUMBER});
}
import 'babel-polyfill'
import { put,takeEvery,all,take,call,fork,cancel } from 'redux-saga/effects';
import { push } from 'react-router-redux'
import * as types from './store/action-types';
let Api = {
login(username,password){
return new Promise(function(resolve){
setTimeout(function(){
resolve('token');
},1000);
});
},
logout(){
return new Promise(function(resolve){
setTimeout(function(){
resolve();
},1);
});
}
}
function* login(dispatch,username,password){
try{
//如果 Api 调用成功了,login 将发起一个 LOGIN_SUCCESS action 然后返回获取到的 token。 如果调用导致了错误,将会发起一个 LOGIN_ERROR action
const token = yield call(Api.login,username,password);
yield put({type:types.LOGIN_SUCCESS,token});
dispatch(push('/logout'));
}catch(error){
yield put({type:types.LOGIN_ERROR,error});
}
}
function* logout(dispatch){
try{
yield call(Api.logout);
yield put({type:types.LOGOUT_SUCCESS});
dispatch(push('/login'));
}catch(error){
yield put({type:types.LOGOUT_ERROR,error});
}
}
function* loginFlow(getState,dispatch){
//loginFlow 在一个 while(true) 循环中实现它所有的流程,这样做的意思是:一旦到达流程最后一步(LOGOUT),通过等待一个新的 LOGIN_REQUEST action 来启动一个新的迭代。
while(true){
//loginFlow 首先等待一个 LOGIN_REQUEST action。 然后在 action 的 payload 中获取有效凭据(即 user 和 password)并调用一个 call 到 login 任务。
//call 不仅可以用来调用返回 Promise 的函数。我们也可以用它来调用其他 Generator 函数
//loginFlow 将等待 login 直到它终止或返回
//redux-saga 提供了另一个 Effect:fork。 当我们 fork 一个 任务,任务会在后台启动,调用者也可以继续它自己的流程,而不用等待被 fork 的任务结束。
let {username,password} = yield take(types.LOGIN_REQUEST);
const task = yield fork(login,dispatch,username,password);
yield take(types.LOGOUT_REQUEST);
yield cancel(task);
yield call(logout,dispatch);
}
}
export default function* rootSaga({subscribe,dispatch,getState}){
yield all([loginFlow(getState,dispatch)]);
}
import reducer from './reducer';
import {createStore,applyMiddleware,compose} from 'redux';
import createSagaMiddleware from 'redux-saga';
import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux';
import createHistory from 'history/createHashHistory';
const history = createHistory();
const router = routerMiddleware(history);
import rootSaga from '../sagas';
let sagaMiddelware = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
let store = composeEnhancers(applyMiddleware(sagaMiddelware,router))(createStore)(reducer);
sagaMiddelware.run(rootSaga,{subscribe:store.subscribe, dispatch:store.dispatch, getState:store.getState});
window.store = store;
export default store;
import * as types from './action-types';
import {combineReducers} from 'redux';
import {routerReducer} from 'react-router-redux'
let user = function(state={token:null,error:null},action){
switch(action.type){
case types.LOGIN_SUCCESS:
return {...state,token:action.token};
case types.LOGIN_ERROR:
return {...state,error:action.error};
case types.LOGOUT_SUCCESS:
return {token:null,error:null};
case types.LOGOUT_ERROR:
return state;
default:
return state;
}
}
export default combineReducers({
user,
router: routerReducer
})
import * as types from './action-types';
export default {
login(username,password){
return {type:types.LOGIN_REQUEST,username,password};
},
logout(){
return {type:types.LOGOUT_REQUEST};
}
}
export const LOGIN_REQUEST = 'LOGIN_REQUEST';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_ERROR = 'LOGIN_ERROR';
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const LOGOUT_ERROR = 'LOGOUT_ERROR';
import React,{Component} from 'react';
import {connect} from 'react-redux';
import actions from '../store/actions';
class Counter extends Component{
handleSubmit = (event)=>{
event.preventDefault();
let username = this.username.value;
let password = this.password.value;
this.props.login(username,password);
}
render(){
return (
<form onSubmit={this.handleSubmit}>
用户名<input type="text" ref={input=>this.username = input}/><br/>
密码<input type="text" ref={input=>this.password = input}/><br/>
<input type="submit"/>
</form>
)
}
}
export default connect(
state=>state.user,
actions
)(Counter);
import React,{Component} from 'react';
import {connect} from 'react-redux';
import actions from '../store/actions';
class Counter extends Component{
handleSubmit = ()=>{
let username = this.username.value;
let password = this.password.value;
this.props.login(username,password);
}
render(){
return (
<div>
token:{this.props.token}{this.props.error}
<button onClick={this.props.logout}>退出</button>
</div>
)
}
}
export default connect(
state=>state.user,
actions
)(Counter);
import React,{Component} from 'react';
import Login from './components/Login';
import Logout from './components/Logout';
import {Provider} from 'react-redux';
import ReactDOM from 'react-dom';
import store from './store';
import { Route,Redirect,Switch} from 'react-router-dom'
import { ConnectedRouter} from 'react-router-redux'
import createHistory from 'history/createHashHistory'
const history = createHistory();
ReactDOM.render((
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route path="/login" component={Login}/>
<Route path="/logout" component={Logout}/>
<Redirect to="/login"/>
</Switch>
</ConnectedRouter>
</Provider>
),document.querySelector('#root'));
export default function* rootSaga({subscribe,dispatch,getState}){
yield all([loginFlow(getState,dispatch)]);
}
有时候我们同时启动多个任务,但又不想等待所有任务完成,我们只希望拿到 胜利者:即第一个被 resolve(或 reject)的任务。 race Effect 提供了一个方法,在多个 Effects 之间触发一个竞赛(race)。
function log(){
return new Promise(function(resolve){
setTimeout(function(){
resolve();
},1000)
});
}
function* backgroundTask(){
while(true){
yield log();
console.log(new Date().toLocaleString());
}
}
function* watchStartTask(){
while(true){
yield take('START_TASK');
yield race({
task: call(backgroundTask),
cancel: take('CANCEL_TASK')
})
}
}
export default function* rootSaga({subscribe,dispatch,getState}){
yield all([loginFlow(getState,dispatch),watchStartTask()]);
}