2018-09-29

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 ...

Markdown is supported

Be the first guy leaving a comment!