redux-saga [
- redux-saga 是一个 redux 的中间件,而中间件的作用是为 redux 提供额外的功能。
- 在 reducers 中的所有操作都是同步的并且是纯粹的,即 reducer 都是纯函数,纯函数是指一个函数的返回结果只依赖于它的参数,并且在执行过程中不会对外部产生副作用,即给它传什么,就吐出什么。
- 但是在实际的应用开发中,我们希望做一些异步的(如Ajax请求)且不纯粹的操作(如改变外部的状态),这些在函数式编程范式中被称为“副作用”。
redux-saga 就是用来处理上述副作用(异步任务)的一个中间件。它是一个接收事件,并可能触发新事件的过程管理者,为你的应用管理复杂的流程。
2. redux-saga工作原理 [#](#t12. redux-saga工作原理)
- sages 采用 Generator 函数来 yield Effects(包含指令的文本对象)
- Generator 函数的作用是可以暂停执行,再次执行的时候从上次暂停的地方继续执行
- Effect 是一个简单的对象,该对象包含了一些给 middleware 解释执行的信息。
- 你可以通过使用 effects API 如 fork,call,take,put,cancel 等来创建 Effect。 redux-saga
3. redux-saga分类 [#](#t23. redux-saga分类)
- worker saga 做左右的工作,如调用API,进行异步请求,获取异步封装结果
- watcher saga 监听被dispatch的actions,当接受到action或者知道其被触发时,调用worker执行任务
- root saga 立即启动saga的唯一入口
4. API [#](#t34. API)
- Middleware API
- createSagaMiddleware(...sagas)
- middleware.run(saga, ...args)
- Saga Helpers
- takeEvery(pattern, saga, ...args)
- takeLatest(pattern, saga, ..args)
- Effect creators
- take(pattern)
- put(action)
- call(fn, ...args)
- call([context, fn], ...args)
- apply(context, fn, args)
- cps(fn, ...args)
- cps([context, fn], ...args)
- fork(fn, ...args)
- fork([context, fn], ...args)
- join(task)
- cancel(task)
- select(selector, ...args)
- Effect combinators
- race(effects)
- [...effects] (aka parallel effects)
- Interfaces
- Task
- External API
- runSaga(iterator, {subscribe, dispatch, getState}, [monitor])
5. 跑通saga [#](#t45. 跑通saga)
5.1 index.js [#](#t55.1 index.js)
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'));
5.2 sagas.js [#](#t65.2 sagas.js)
src/sagas.js
export function* rootSaga() {
console.log('rootSaga');
}
5.3 Counter.js [#](#t75.3 Counter.js)
src/components/Counter.js
import React,{Component} from 'react'
import {connect} from 'react-redux';
import actions from '../store/actions';
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);
5.4 index.js [#](#t85.4 index.js)
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;
5.5 actions.js [#](#t95.5 actions.js)
src/store/actions.js
import * as types from './action-types';
export default {
increment() {
return {type:types.INCREMENT}
}
}
5.6 action-types.js [#](#t105.6 action-types.js)
src/store/action-types.js
export const INCREMENT='INCREMENT';
5.7 reducer.js [#](#t115.7 reducer.js)
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;
}
}
6. 实现计数器 [#](#t126. 实现计数器)
6.1 src/saga.js [#](#t136.1 src/saga.js)
import 'babel-polyfill';
import {takeEvery} from 'redux-saga/effects';
import * as types from './store/action-types';
export function* increment(dispatch,action) {
setTimeout(function () {
dispatch({type:types.INCREMENT});
},1000);
}
export function* watchIncrement(dispatch) {
yield takeEvery(types.INCREMENT_ASYNC,increment,dispatch);
}
export function* rootSaga({dispatch,getState}) {
yield watchIncrement(dispatch);
}
6.2 action-types.js [#](#t146.2 action-types.js)
src/store/action-types.js
export const INCREMENT_ASYNC='INCREMENT_ASYNC';
export const INCREMENT='INCREMENT';
6.3 actions.js [#](#t156.3 actions.js)
src/store/actions.js
import * as types from './action-types';
export default {
increment() {
return {type:types.INCREMENT_ASYNC}
}
}
6.4 index.js [#](#t166.4 index.js)
rc/store/index.js
let store=applyMiddleware(sagaMiddleware)(createStore)(reducer);
+ sagaMiddleware.run(rootSaga,store);
window.store=store;
7. 简化计数器 [#](#t177. 简化计数器)
import 'babel-polyfill';
import {takeEvery,put} from 'redux-saga/effects';
import * as types from './store/action-types';
const delay=ms => new Promise((resolve,reject) => {
setTimeout(function () {
resolve();
},1000);
});
export function* increment() {
yield delay(1000);
yield put({type: types.INCREMENT});
}
export function* watchIncrement() {
yield takeEvery(types.INCREMENT_ASYNC,increment);
}
export function* rootSaga() {
yield watchIncrement();
}
#
8.日志记录器import 'babel-polyfill';
import {takeEvery,put,all,take} from 'redux-saga/effects';
import * as types from './store/action-types';
const delay=ms => new Promise((resolve,reject) => {
setTimeout(function () {
resolve();
},1000);
});
export function* increment() {
yield delay(1000);
yield put({type: types.INCREMENT});
}
export function* watchIncrement() {
yield takeEvery(types.INCREMENT_ASYNC,increment);
}
export function* watchIncrementMax() {
//yield takeEvery(types.INCREMENT_ASYNC,increment);
for (let i=0;i<3;i++){
let action=yield take(types.INCREMENT_ASYNC);
yield increment();
}
console.log('已经到达了最大值!');
}
export function* logger(getState,action) {
console.log(getState(),action);
}
export function* watchAndLogger(getState) {
yield takeEvery('*',logger,getState);
}
export function* rootSaga({getState}) {
yield all([watchIncrementMax(),watchAndLogger(getState)]);
}
9. 实现take&put [#](#t199. 实现take&put)
9.1 saga.js [#](#t209.1 saga.js)
src/saga.js
import 'babel-polyfill';
import {take,put} from './redux-saga/effects';
import * as types from './store/action-types';
export function* rootSaga() {
let action=yield take(types.INCREMENT_ASYNC);
console.log(action);
yield put({type:types.INCREMENT});
}
9.2 index.js [#](#t219.2 index.js)
src/store/index.js
import createSagaMiddleware from '../redux-saga';
import {rootSaga} from '../saga';
let sagaMiddleware=createSagaMiddleware();
let store=applyMiddleware(sagaMiddleware)(createStore)(reducer);
sagaMiddleware.run(rootSaga,store);
9.3 index.js [#](#t229.3 index.js)
src/redux-saga/index.js
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,store) {
let it=generator();
function next(input) {
let {value: effect,done}=it.next(input);
if (!done) {
switch (effect.type) {
case 'take':
let {actionType}=effect;
channel.subscribe(actionType,next);
break;
case 'put':
let {action}=effect;
dispatch(action);
next();
default:
break;
}
}
}
next();
}
sagaMiddleware.run=run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
export default createSagaMiddleware;
9.4 effects.js [#](#t239.4 effects.js)
src/redux-saga/effects.js
export function take(actionType) {
return {
type: 'take',
actionType
}
}
export function put(action) {
return {
type: 'put',
action
}
}
10. 实现takeEvery [#](#t2410. 实现takeEvery)
10.1 effects.js [#](#t2510.1 effects.js)
src/redux-saga/effects.js
export function take(actionType) {
return {
type: 'take',
actionType
}
}
export function put(action) {
return {
type: 'put',
action
}
}
export function fork(cb) {
return {
type: 'fork',
cb
}
}
export function* takeEvery(actionType,cb) {
yield fork(function* () {
while (true) {
let action=yield take(actionType);
yield cb(action);
}
});
}
10.2 index.js [#](#t2610.2 index.js)
src/redux-saga/index.js
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(input) {
let {value: effect,done}=it.next(input);
if (!done) {
if (typeof effect[Symbol.iterator] == 'function') {
run(effect);
next();
} else {
switch (effect.type) {
case 'take':
let {actionType}=effect;
channel.subscribe(actionType,next);
break;
case 'put':
let {action}=effect;
dispatch(action);
next();
break;
case 'fork':
let {cb}=effect;
run(cb);
next();
break;
default:
break;
}
}
}
}
next();
}
sagaMiddleware.run=run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
export default createSagaMiddleware;
10.3 saga.js [#](#t2710.3 saga.js)
src/saga.js
import 'babel-polyfill';
import {take,put,takeEvery} from './redux-saga/effects';
import * as types from './store/action-types';
export function* increment(action) {
yield put({type: types.INCREMENT});
}
export function* rootSaga() {
yield takeEvery(types.INCREMENT_ASYNC,increment);
console.log('next');
}
11. 支持Promise [#](#t2811. 支持Promise)
11.1 effects.js [#](#t2911.1 effects.js)
src/redux-saga/effects.js
export function call(fn,...args) {
return {
type: 'call',
fn,
args
}
}
11.2 index.js [#](#t3011.2 index.js)
src/redux-saga/index.js
case 'call':
let {fn,args}=effect;
fn(...args).then(next);
11.3 saga.js [#](#t3111.3 saga.js)
src/saga.js
const delay=ms => new Promise((resolve,reject) => {
setTimeout(function () {
resolve();
},ms);
})
export function* increment(action) {
yield call(delay,1000);
yield put({type: types.INCREMENT});
}
12. 登录注册 [#](#t3212. 登录注册)
12.1 src3\sagas.js [#](#t3312.1 src3\sagas.js)
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)]);
}
12.2 src3\store\index.js [#](#t3412.2 src3\store\index.js)
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;
12.3 src3/store/reducer.js [#](#t3512.3 src3/store/reducer.js)
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
})
12.4 src3/store/actions.js [#](#t3612.4 src3/store/actions.js)
import * as types from './action-types';
export default {
login(username,password){
return {type:types.LOGIN_REQUEST,username,password};
},
logout(){
return {type:types.LOGOUT_REQUEST};
}
}
12.5 src3/store/action-types.js [#](#t3712.5 src3/store/action-types.js)
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';
12.6 src3/components/Login.js [#](#t3812.6 src3/components/Login.js)
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);
12.7 src3/components/Logout.js [#](#t3912.7 src3/components/Logout.js)
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);
12.8 src3/index.js [#](#t4012.8 src3/index.js)
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'));
13. 资源 [#](#t4113. 资源)
13.1 Iterator [#](#t4213.1 Iterator)
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。 Iterator 的作用有三个:
- 一是为各种数据结构,提供一个统一的、简便的访问接口;
- 二是使得数据结构的成员能够按某种次序排列;
- 三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
原生具备 Iterator 接口的数据结构如下。
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象
const obj = { [Symbol.iterator] : function () { return { next: function () { return { value: 1, done: true }; } }; } };
Gitalking ...
Be the first guy leaving a comment!