• Redux-saga使用教程详解


    edux-saga使用心得总结(包含样例代码),本文的样例代码地址:样例代码地址 ,欢迎star


    最近将项目中redux的中间件,从redux-thunk替换成了redux-saga,做个笔记总结一下redux-saga的使用心得,阅读本文需要了解什么是redux,redux中间件的用处是什么?如果弄懂上述两个概念,就可以继续阅读本文。

    • redux-thunk处理副作用的缺点
    • redux-saga写一个hellosaga
    • redux-saga的使用技术细节
    • redux-saga实现一个登陆和列表样例

    1.redux-thunk处理副作用的缺点

    (1)redux的副作用处理

    redux中的数据流大致是:

    UI—————>action(plain)—————>reducer——————>state——————>UI

    default

    redux是遵循函数式编程的规则,上述的数据流中,action是一个原始js对象(plain object)且reducer是一个纯函数,对于同步且没有副作用的操作,上述的数据流起到可以管理数据,从而控制视图层更新的目的。

    但是如果存在副作用,比如ajax异步请求等等,那么应该怎么做?

    如果存在副作用函数,那么我们需要首先处理副作用函数,然后生成原始的js对象。如何处理副作用操作,在redux中选择在发出action,到reducer处理函数之间使用中间件处理副作用。

    redux增加中间件处理副作用后的数据流大致如下:

    UI——>action(side function)—>middleware—>action(plain)—>reducer—>state—>UI

    default

    在有副作用的action和原始的action之间增加中间件处理,从图中我们也可以看出,中间件的作用就是:

    转换异步操作,生成原始的action,这样,reducer函数就能处理相应的action,从而改变state,更新UI。

    (2)redux-thunk

    在redux中,thunk是redux作者给出的中间件,实现极为简单,10多行代码:

    function createThunkMiddleware(extraArgument) {
      return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }
    
        return next(action);
      };
    }
    
    const thunk = createThunkMiddleware();
    thunk.withExtraArgument = createThunkMiddleware;
    
    export default thunk;
    

    这几行代码做的事情也很简单,判别action的类型,如果action是函数,就调用这个函数,调用的步骤为:

    action(dispatch, getState, extraArgument);
    

    发现实参为dispatch和getState,因此我们在定义action为thunk函数是,一般形参为dispatch和getState。

    (3)redux-thunk的缺点

    hunk的缺点也是很明显的,thunk仅仅做了执行这个函数,并不在乎函数主体内是什么,也就是说thunk使
    得redux可以接受函数作为action,但是函数的内部可以多种多样。比如下面是一个获取商品列表的异步操作所对应的action:

    export default ()=>(dispatch)=>{
        fetch('/api/goodList',{ //fecth返回的是一个promise
          method: 'get',
          dataType: 'json',
        }).then(function(json){
          var json=JSON.parse(json);
          if(json.msg==200){
            dispatch({type:'init',data:json.data});
          }
        },function(error){
          console.log(error);
        });
    };
    

    从这个具有副作用的action中,我们可以看出,函数内部极为复杂。如果需要为每一个异步操作都如此定义一个action,显然action不易维护。

    action不易维护的原因:

    • action的形式不统一
    • 就是异步操作太为分散,分散在了各个action中

    2.redux-saga写一个hellosaga

    跟redux-thunk,redux-saga是控制执行的generator,在redux-saga中action是原始的js对象,把所有的异步副作用操作放在了saga函数里面。这样既统一了action的形式,又使得异步操作集中可以被集中处理。

    redux-saga是通过genetator实现的,如果不支持generator需要通过插件babel-polyfill转义。我们接着来实现一个输出hellosaga的例子。

    (1)创建一个helloSaga.js文件

    export function * helloSaga() {
      console.log('Hello Sagas!');
    }
    

    (2)在redux中使用redux-saga中间件

    在main.js中:

    import { createStore, applyMiddleware } from 'redux'
    import createSagaMiddleware from 'redux-saga'
    import { helloSaga } from './sagas'
    const sagaMiddleware=createSagaMiddleware();
    const store = createStore(
     reducer,
     applyMiddleware(sagaMiddleware)
    );
    sagaMiddleware.run(helloSaga);
    //会输出Hello, Sagas!
    

    和调用redux的其他中间件一样,如果想使用redux-saga中间件,那么只要在applyMiddleware中调用一个createSagaMiddleware的实例。唯一不同的是需要调用run方法使得generator可以开始执行。

    3.redux-saga的使用技术细节

    redux-saga除了上述的action统一、可以集中处理异步操作等优点外,redux-saga中使用声明式的Effect以及提供了更加细腻的控制流。

    (1)声明式的Effect

    redux-saga中最大的特点就是提供了声明式的Effect,声明式的Effect使得redux-saga监听原始js对象形式的action,并且可以方便单元测试,我们一一来看。

    • 首先,在redux-saga中提供了一系列的api,比如take、put、all、select等API ,在redux-saga中将这一系列的api都定义为Effect。这些Effect执行后,当函数resolve时返回一个描述对象,然后redux-saga中间件根据这个描述对象恢复执行generator中的函数。

    首先来看redux-thunk的大体过程:

    action1(side function)—>redux-thunk监听—>执行相应的有副作用的方法—>action2(plain object)

    2

    转化到action2是一个原始js对象形式的action,然后执行reducer函数就会更新store中的state。

    而redux-saga的大体过程如下:

    action1(plain object)——>redux-saga监听—>执行相应的Effect方法——>返回描述对象—>恢复执行异步和副作用函数—>action2(plain object)

    default

    对比redux-thunk我们发现,redux-saga中监听到了原始js对象action,并不会马上执行副作用操作,会先通过Effect方法将其转化成一个描述对象,然后再将描述对象,作为标识,再恢复执行副作用函数。

    通过使用Effect类函数,可以方便单元测试,我们不需要测试副作用函数的返回结果。只需要比较执行Effect方法后返回的描述对象,与我们所期望的描述对象是否相同即可。

    举例来说,call方法是一个Effect类方法:

    import { call } from 'redux-saga/effects'
    
    function* fetchProducts() {
      const products = yield call(Api.fetch, '/products')
      // ...
    }
    

    上述代码中,比如我们需要测试Api.fetch返回的结果是否符合预期,通过调用call方法,返回一个描述对象。这个描述对象包含了所需要调用的方法和执行方法时的实际参数,我们认为只要描述对象相同,也就是说只要调用的方法和执行该方法时的实际参数相同,就认为最后执行的结果肯定是满足预期的,这样可以方便的进行单元测试,不需要模拟Api.fetch函数的具体返回结果。

    import { call } from 'redux-saga/effects'
    import Api from '...'
    
    const iterator = fetchProducts()
    
    // expects a call instruction
    assert.deepEqual(
      iterator.next().value,
      call(Api.fetch, '/products'),
      "fetchProducts should yield an Effect call(Api.fetch, './products')"
    )
    

    (2)Effect提供的具体方法

    下面来介绍几个Effect中常用的几个方法,从低阶的API,比如take,call(apply),fork,put,select等,以及高阶API,比如takeEvery和takeLatest等,从而加深对redux-saga用法的认识(这节可能比较生涩,在第三章中会结合具体的实例来分析,本小节先对各种Effect有一个初步的了解)。

    引入:

    import {take,call,put,select,fork,takeEvery,takeLatest} from 'redux-saga/effects'
    
    • take

    take这个方法,是用来监听action,返回的是监听到的action对象。比如:

    const loginAction = {
       type:'login'
    }
    

    在UI Component中dispatch一个action:

    dispatch(loginAction)
    

    在saga中使用:

    const action = yield take('login');
    

    可以监听到UI传递到中间件的Action,上述take方法的返回,就是dipath的原始对象。一旦监听到login动作,返回的action为:

    {
      type:'login'
    }
    
    • call(apply)

    call和apply方法与js中的call和apply相似,我们以call方法为例:

    call(fn, ...args)
    

    call方法调用fn,参数为args,返回一个描述对象。不过这里call方法传入的函数fn可以是普通函数,也可以是generator。call方法应用很广泛,在redux-saga中使用异步请求等常用call方法来实现。

    yield call(fetch,'/userInfo',username)
    
    • put

    在前面提到,redux-saga做为中间件,工作流是这样的:

    UI——>action1————>redux-saga中间件————>action2————>reducer..

    从工作流中,我们发现redux-saga执行完副作用函数后,必须发出action,然后这个action被reducer监听,从而达到更新state的目的。相应的这里的put对应与redux中的dispatch,工作流程图如下:

    default

    从图中可以看出redux-saga执行副作用方法转化action时,put这个Effect方法跟redux原始的dispatch相似,都是可以发出action,且发出的action都会被reducer监听到。put的使用方法:

     yield put({type:'login'})
    
    • select

    put方法与redux中的dispatch相对应,同样的如果我们想在中间件中获取state,那么需要使用select。select方法对应的是redux中的getState,用户获取store中的state,使用方法:

    const state= yield select()
    
    • fork

    fork方法在第三章的实例中会详细的介绍,这里先提一笔,fork方法相当于web work,fork方法不会阻塞主线程,在非阻塞调用中十分有用。

    • takeEvery和takeLatest

    takeEvery和takeLatest用于监听相应的动作并执行相应的方法,是构建在take和fork上面的高阶api,比如要监听login动作,好用takeEvery方法可以:

    takeEvery('login',loginFunc)
    

    takeEvery监听到login的动作,就会执行loginFunc方法,除此之外,takeEvery可以同时监听到多个相同的action。

    takeLatest方法跟takeEvery是相同方式调用:

    takeLatest('login',loginFunc)
    

    与takeLatest不同的是,takeLatest是会监听执行最近的那个被触发的action。

    4.redux-saga实现一个登陆和列表样例

    接着我们来实现一个redux-saga样例,存在一个登陆页,登陆成功后,显示列表页,并且,在列表页,可

    以点击登出,返回到登陆页。例子的最终展示效果如下:

    login

    样例的功能流程图为:

    default

    接着我们按照上述的流程来一步步的实现所对应的功能。

    (1)LoginPanel(登陆页)

    登陆页的功能包括

    • 输入时时保存用户名
    • 输入时时保存密码
    • 点击sign in 请求判断是否登陆成功

    I)输入时时保存用户名和密码

    用户名输入框和密码框onchange时触发的函数为:

     changeUsername:(e)=>{
        dispatch({type:'CHANGE_USERNAME',value:e.target.value});
     },
    changePassword:(e)=>{
      dispatch({type:'CHANGE_PASSWORD',value:e.target.value});
    }
    

    在函数中最后会dispatch两个action:CHANGE_USERNAME和CHANGE_PASSWORD

    在saga.js文件中监听这两个方法并执行副作用函数,最后put发出转化后的action,给reducer函数调用:

    function * watchUsername(){
      while(true){
        const action= yield take('CHANGE_USERNAME');
        yield put({type:'change_username',
        value:action.value});
      }
    }
    function * watchPassword(){
      while(true){
        const action=yield take('CHANGE_PASSWORD');
        yield put({type:'change_password',
        value:action.value});
      }
    }
    

    最后在reducer中接收到redux-saga的put方法传递过来的action:change_username和change_password,然后更新state。

    II)监听登陆事件判断登陆是否成功

    在UI中发出的登陆事件为:

    toLoginIn:(username,password)=>{
      dispatch({type:'TO_LOGIN_IN',username,password});
    }
    

    登陆事件的action为:TO_LOGIN_IN.对于登入事件的处理函数为:

     while(true){
        //监听登入事件
        const action1=yield take('TO_LOGIN_IN');
        const res=yield call(fetchSmart,'/login',{
          method:'POST',
          body:JSON.stringify({
            username:action1.username,
            password:action1.password
        })
        if(res){
          put({type:'to_login_in'});
        }
    });
    

    在上述的处理函数中,首先监听原始动作提取出传递来的用户名和密码,然后请求是否登陆成功,如果登陆成功有返回值,则执行put的action:to_login_in.

    (2) LoginSuccess(登陆成功列表展示页)

    登陆成功后的页面功能包括:

    • 获取列表信息,展示列表信息
    • 登出功能,点击可以返回登陆页面

    I)获取列表信息

    import {delay} from 'redux-saga';
    
    function * getList(){
      try {
       yield delay(3000);
       const res = yield call(fetchSmart,'/list',{
         method:'POST',
         body:JSON.stringify({})
       });
       yield put({type:'update_list',list:res.data.activityList});
     } catch(error) {
       yield put({type:'update_list_error', error});
     }
    }
    

    为了演示请求过程,我们在本地mock,通过redux-saga的工具函数delay,delay的功能相当于延迟xx秒,因为真实的请求存在延迟,因此可以用delay在本地模拟真实场景下的请求延迟。

    II)登出功能

    const action2=yield take('TO_LOGIN_OUT');
    yield put({type:'to_login_out'});
    

    与登入相似,登出的功能从UI处接受action:TO_LOGIN_OUT,然后转发action:to_login_out

    (3) 完整的实现登入登出和列表展示的代码

    function * getList(){
      try {
       yield delay(3000);
       const res = yield call(fetchSmart,'/list',{
         method:'POST',
         body:JSON.stringify({})
       });
       yield put({type:'update_list',list:res.data.activityList});
     } catch(error) {
       yield put({type:'update_list_error', error});
     }
    }
    
    function * watchIsLogin(){
      while(true){
        //监听登入事件
        const action1=yield take('TO_LOGIN_IN');
        
        const res=yield call(fetchSmart,'/login',{
          method:'POST',
          body:JSON.stringify({
            username:action1.username,
            password:action1.password
          })
        });
        
        //根据返回的状态码判断登陆是否成功
        if(res.status===10000){
          yield put({type:'to_login_in'});
          //登陆成功后获取首页的活动列表
          yield call(getList);
        }
        
        //监听登出事件
        const action2=yield take('TO_LOGIN_OUT');
        yield put({type:'to_login_out'});
      }
    }
    

    通过请求状态码判断登入是否成功,在登陆成功后,可以通过:

    yield call(getList)
    

    的方式调用获取活动列表的函数getList。这样咋一看没有什么问题,但是注意call方法调用是会阻塞主线程的,具体来说:

    • 在call方法调用结束之前,call方法之后的语句是无法执行的

    • 如果call(getList)存在延迟,call(getList)之后的语句 const action2=yieldtake('TO_LOGIN_OUT')在call方法返回结果之前无法执行

    • 在延迟期间的登出操作会被忽略。

    用框图可以更清楚的分析:

    default

    call方法调用阻塞主线程的具体效果如下动图所示:

    login_1

    白屏时为请求列表的等待时间,在此时,我们点击登出按钮,无法响应登出功能,直到请求列表成功,展示列表信息后,点击登出按钮才有相应的登出功能。也就是说call方法阻塞了主线程。

    (4) 无阻塞调用

    我们在第二章中,介绍了fork方法可以类似与web work,fork方法不会阻塞主线程。应用于上述例子,我们可以将:

    yield call(getList)
    

    修改为:

    yield fork(getList)
    

    这样展示的结果为:

    login_2

    通过fork方法不会阻塞主线程,在白屏时点击登出,可以立刻响应登出功能,从而返回登陆页面。

    5.总结

    通过上述章节,我们可以概括出redux-saga做为redux中间件的全部优点:

    • 统一action的形式,在redux-saga中,从UI中dispatch的action为原始对象

    • 集中处理异步等存在副作用的逻辑

    • 通过转化effects函数,可以方便进行单元测试

    • 完善和严谨的流程控制,可以较为清晰的控制复杂的逻辑。

     转自:https://github.com/forthealllight/blog/issues/14

    ==========================================================================================

    Redux-saga

    概述

    redux-saga是一个用于管理redux应用异步操作的中间件,redux-saga通过创建sagas将所有异步操作逻辑收集在一个地方集中处理,可以用来代替redux-thunk中间件。

    • 这意味着应用的逻辑会存在两个地方
      (1) reducer负责处理action的stage更新
      (2) sagas负责协调那些复杂或者异步的操作
    • sagas是通过generator函数来创建的
    • sagas可以被看作是在后台运行的进程。sagas监听发起的action,然后决定基于这个action来做什么 (比如:是发起一个异步请求,还是发起其他的action到store,还是调用其他的sagas 等 )
    • 在redux-saga的世界里,所有的任务都通过用 yield Effects 来完成 ( effect可以看作是redux-saga的任务单元 )
    • Effects 都是简单的 javascript对象,包含了要被 saga middleware 执行的信息
    • redux-saga 为各项任务提供了各种 ( Effects创建器 )
    • 因为使用了generator函数,redux-saga让你可以用 同步的方式来写异步代码
    • redux-saga启动的任务可以在任何时候通过手动来取消,也可以把任务和其他的Effects放到 race 方法里以自动取消

    React+Redux Cycle(来源:https://www.youtube.com/watch?v=1QI-UE3-0PU)

    • produce: 生产
    • flow: 流动,排出
    • 整个流程:ui组件触发action创建函数 ---> action创建函数返回一个action ------> action被传入redux中间件(被 saga等中间件处理) ,产生新的action,传入reducer-------> reducer把数据传给ui组件显示 -----> mapStateToProps ------> ui组件显示

    安装

    yarn add redux-saga 
    // cnpm install redux-saga -S
    

    实例

    (1) 配置

    store.js
    
    
    import {createStore,combineReducers, applyMiddleware} from 'redux';
    import userNameReducer from '../username/reducer.js';
    import createSagaMiddleware from 'redux-saga';       // 引入redux-saga中的createSagaMiddleware函数
    import rootSaga from './saga.js';                    // 引入saga.js
    
    const sagaMiddleware = createSagaMiddleware()        // 执行
    
    const reducerAll = {
        userNameReducer: userNameReducer
    }
    
    
    export const store = createStore(
        combineReducers({...reducerAll}),               // 合并reducer
        window.devToolsExtension ? window.devToolsExtension() : undefined,    // dev-tools
        applyMiddleware(sagaMiddleware)                 // 中间件,加载sagaMiddleware
    )
    
    sagaMiddleware.run(rootSaga)                        // 执行rootSaga
    
    

    (2) ui组件触发action创建函数

    username.js
    
    
    
    import React from 'react';
    
    export default class User extends React.Component {
        componentDidMount() {
            console.log(this.props)
        }
        go = () => {
            this.props.getAges(3)           // 发起action,传入参数
        }
        render() {
            return (
                <div>
                    这是username组件
                    <div>
                        {
                            this.props.username.userNameReducer.username
                        }
                    </div>
                    <br/>
                    <div onClick={this.go}>
                        点击获取提交age
                    </div>
                </div>
            )
        }
    }
    
    

    (3) action创建函数,返回action ----> 传入saga ( 如果没有saga,就该传入reducer )

    action.js
    
    
    
    import { actionTypes } from './constant.js';
    
    export function getAges(age) {
        console.log(age,'age') // 3
        return {
            type: actionTypes.GET_AGE,
            payload: age
        }
    }
    

    (4) saga.js ------> 捕获action创建函数返回的action

    saga.js
    
    
    
    
    import { actionTypes } from '../username/constant.js';
    import {call, put, takeEvery} from 'redux-saga/effects';     // 引入相关函数
    
    function* goAge(action){    // 参数是action创建函数返回的action
        console.log(action)
        const p = function() {
            return fetch(`http://image.baidu.com/channel/listjson?rn=${action.payload}...`,{
                method: 'GET'
            })
            .then(res => res.json())
            .then(res => {
                console.log(res)
                return res
            })
        }
        const res = yield call(p)    // 执行p函数,返回值赋值给res
    
        yield put({      // dispatch一个action到reducer, payload是请求返回的数据
            type: actionTypes.GET_AGE_SUCCESS,
            payload: res   
        })
    }
    
    function* rootSaga() {     // 在store.js中,执行了 sagaMiddleware.run(rootSaga)
        yield takeEvery(actionTypes.GET_AGE, goAge)   // 如果有对应type的action触发,就执行goAge()函数
    }
    
    export default rootSaga;      // 导出rootSaga,被store.js文件import
    

    (5) 然后由ui组件从reducer中获取数据,并显示。


    名词解释

    Effect

    一个effect就是一个纯文本javascript对象,包含一些将被saga middleware执行的指令。

    • 如何创建 effect ?
      使用redux-saga提供的 工厂函数 来创建effect
    比如:
    
    你可以使用  call(myfunc,  'arg1', 'arg2')  指示middleware调用  myfunc('arg1', 'arg2')
    
    并将结果返回给 yield 了 effect  的那个  generator
    

    Task

    一个 task 就像是一个在后台运行的进程,在基于redux-saga的应用程序中,可以同时运行多个task

    • 通过 fork 函数来创建 task
    function* saga() {
      ...
      const task = yield fork(otherSaga, ...args)
      ...
    }
    

    阻塞调用 和 非组塞调用

    • 阻塞调用
      阻塞调用的意思是: saga 会在 yield 了 effect 后会等待其执行结果返回,结果返回后才恢复执行 generator 中的下一个指令
    • 非阻塞调用
      非阻塞调用的意思是: saga 会在 yield effect 之后立即恢复执行

    watcher 和 worker

    指的是一种使用两个单独的saga来组织控制流的方式

    • watcher:监听发起的action 并在每次接收到action时 fork 一个 work
    • worker: 处理action,并结束它

    api

    createSagaMiddleware(...sagas)

    createSagaMiddleware的作用是创建一个redux中间件,并将sagas与Redux store建立链接

    • 参数是一个数组,里面是generator函数列表
    • sagas: Array ---- ( generator函数列表 )

    middleware.run(saga, ...args)

    动态执行 saga。用于 applyMiddleware 阶段之后执行 Sagas。这个方法返回一个
    Task 描述对象。

    • saga: Function: 一个 Generator 函数
    • args: Array: 提供给 saga 的参数 (除了 Store 的 getState 方法)

    take(pattern)

    ----- 暂停Generator,匹配的action被发起时,恢复执行

    创建一条 Effect 描述信息,指示 middleware 等待 Store 上指定的 action。 Generator 会暂停,直到一个与 pattern 匹配的 action 被发起。
    pattern的规则
    (1) pattern为空 或者 * ,将会匹配所有发起的action

    (2) pattern是一个函数,action 会在 pattern(action) 返回为 true 时被匹配
    (例如,take(action => action.entities) 会匹配那些 entities 字段为真的 action)。

    (3) pattern是一个字符串,action 会在 action.type === pattern 时被匹配

    (4) pattern是一个数组,会针对数组所有项,匹配与 action.type 相等的 action
    (例如,take([INCREMENT, DECREMENT]) 会匹配 INCREMENT 或 DECREMENT 类型的 action)

    take实例:

    username.js
    
    
    
    import React from 'react';
    
    
    export default class User extends React.Component {
        go = () => {
            new Promise((resolve,reject) => {
                resolve(3)
            }).then(res => this.props.getAges(res))    // 执行action.js中的getAges函数
                .then(res => {
                    setTimeout(()=> {
                        console.log('5s钟后才会执行settimeout')
                        this.props.settimeout()
                    },5000)           // 在getAges函数执行完后,再过5s执行,settimeout()函数
                }) 
            
            
        }
        render() {
            console.log(this.props, 'this.props')
            return (
                <div>
                    这是username组件
                    <br/>
                    <div onClick={this.go}>
                        点击获取提交age
                    </div>
                    <div>
                        {
                            this.props.username && 
                            this.props.username.userNameReducer.image.data && 
                            this.props.username.userNameReducer.image.data.map(
                                (item,key) => <div key={key}>{item.abs }</div>
                            )
                        }
                    </div>
                </div>
            )
        }
    }
    
    action.js
    
    
    
    import { actionTypes } from './constant.js';
    
    
    export function getAges(age) {
        console.log(age,'age')
        return {
            type: actionTypes.GET_AGE,   // 在saga中有对应的actionTypes.GET_AGE
            payload: age
        }
    }
    
    export function settimeout() {
        return {
            type: actionTypes.MATCH_TAKE,  // 在saga中有对应的actionTypes.MATCH_TAKE,
        }
    }
    
    
    saga.js
    
    
    
    import { actionTypes } from '../username/constant.js';
    import {call, put, takeEvery, take} from 'redux-saga/effects';
    
    function* goAge(action){
        console.log(action)
        const p = function() {
            return fetch(
                `http://image.baidu.com/channel/listjson?pn=0&rn=${action.payload}`,{
                method: 'GET'
            })
            .then(res => res.json())
            .then(res => {
                console.log(res)
                return res
            })
        }
        const res = yield call(p)
        yield take(actionTypes.MATCH_TAKE)   
    
        // generator执行到take时,会暂停执行,直到有type为MATCH_TAKE的action发起时,才恢复执行
    
        // 这里的效果就是点击按钮 5s钟后 才显示请求到的内容,( 5s钟后才执行下面的put语句 )
        yield put({
            type: actionTypes.GET_AGE_SUCCESS,
            payload: res
        })
    }
    
    function* rootSaga() {
        yield takeEvery(actionTypes.GET_AGE, goAge)  
              
        // 有对应的type是GET_AGE的action发起时,执行goAge() Generator函数
    }
    
    export default rootSaga;
    
    
    

    fork(fn, ...args)

    ----- 无阻塞的执行fn,执行fn时,不会暂停Generator

    ----- yield fork(fn ...args)的结果是一个 Task 对象

    task对象 ---------- 一个具备某些有用的方法和属性的对象

    创建一条 Effect 描述信息,指示 middleware 以 无阻塞调用 方式执行 fn。

    • fn: Function - 一个 Generator 函数, 或者返回 Promise 的普通函数

    • args: Array - 一个数组,作为 fn 的参数

    • fork 类似于 call,可以用来调用普通函数和 Generator 函数。但 fork 的调用是无阻塞的,在等待 fn 返回结果时,middleware 不会暂停 Generator。 相反,一旦 fn 被调用,Generator 立即恢复执行。

    • forkrace 类似,是一个中心化的 Effect,管理 Sagas 间的并发。
      yield fork(fn ...args) 的结果是一个 Task 对象 —— 一个具备某些有用的方法和属性的对象。

    • fork: 是分叉,岔路的意思 ( 并发 )

    实例:

    
    
    import { actionTypes } from '../username/constant.js';
    import {call, put, takeEvery, take, fork} from 'redux-saga/effects';
    
    function* goAge(action){
    
        function* x() {
            yield setTimeout(() => {
               console.log('该显示会在获得图片后,2s中后显示') 
            }, 2000);
        }
    
        const p = function() {
            return fetch(`http://image.baidu.com/channel/listjson?pn=0&rn=${action.payload}`,{
                method: 'GET'
            })
            .then(res => res.json())
            .then(res => {
                console.log(res)
                return res
            })
        }
        const res = yield call(p)
    
        yield take(actionTypes.MATCH_TAKE)   // 阻塞,直到匹配的action触发,才会恢复执行
    
        yield fork(x)  // 无阻塞执行,即x()generator触发后,就会执行下面的put语句
    
        yield put({
            type: actionTypes.GET_AGE_SUCCESS,
            payload: res
        })
    
    }
    
    function* rootSaga() {
        yield takeEvery(actionTypes.GET_AGE, goAge)
    }
    
    export default rootSaga;
    
    

    join(task)

    ----- 等待fork任务返回结果(task对象)

    创建一条 Effect 描述信息,指示 middleware 等待之前的 fork 任务返回结果。

    • task: Task - 之前的 fork 指令返回的 Task 对象
    • yield fork(fn, ...args) 返回的是一个 task 对象

    cancel(task)

    创建一条 Effect 描述信息,指示 middleware 取消之前的 fork 任务。

    • task: Task - 之前的 fork 指令返回的 Task 对象
    • cancel 是一个无阻塞 Effect。也就是说,Generator 将在取消异常被抛出后立即恢复。

    select(selector, ...args)

    ----- 得到 Store 中的 state 中的数据

    创建一条 Effect 描述信息,指示 middleware 调用提供的选择器获取 Store state 上的数据(例如,返回 selector(getState(), ...args) 的结果)。

    • selector: Function - 一个 (state, ...args) => args 函数. 通过当前 state 和一些可选参数,返回当前 Store state 上的部分数据。

    • args: Array - 可选参数,传递给选择器(附加在 getState 后)

    • 如果 select 调用时参数为空( --- 即 yield select() --- ),那 effect 会取得整个的 state
      (和调用 getState() 的结果一样)

    重要提醒:在发起 action 到 store 时,middleware 首先会转发 action 到 reducers 然后通知 Sagas。这意味着,当你查询 Store 的 state, 你获取的是 action 被处理之后的 state。


    put(action)

    ----- 发起一个 action 到 store

    创建一条 Effect 描述信息,指示 middleware 发起一个 action 到 Store。

    • put 是异步的,不会立即发生

    call(fn, ...args) 阻塞执行,call()执行完,才会往下执行

    ----- 执行 fn(...args)

    ----- 对比 fork(fn, ...args) 无阻塞执行

    创建一条 Effect 描述信息,指示 middleware 调用 fn 函数并以 args 为参数。

    fn: Function - 一个 Generator 函数, 或者返回 Promise 的普通函数

    args: Array - 一个数组,作为 fn 的参数

    • fn 既可以是一个普通函数,也可以是一个 Generator 函数

    race(effects)

    • effects: Object : 一个{label: effect, ...}形式的字典对象

    同时执行多个任务

    当我们需要 yield 一个包含 effects 的数组, generator 会被阻塞直到所有的 effects 都执行完毕,或者当一个 effect 被拒绝 (就像 Promise.all 的行为)时,才会恢复执行Generator函数 ( yield后面的语句 )。

    import { call } from 'redux-saga/effects'
    
    
    // 正确写法, effects 将会同步执行
    const [users, repos] = yield [
      call(fetch, '/users'),
      call(fetch, '/repos')
    ]
    
    
    ---------------------------------------------------------------------
    
    // 错误写法
    const users = yield call(fetch, '/users'),
    const repos = yield call(fetch, '/repos')
    
    // call会阻塞执行,第二个 effect 将会在第一个 call 执行完毕才开始。
    
    

    throttle ------ 节流阀的意思

    throttle(ms, pattern, saga, ...args)

    • 用途,是在处理任务时,无视给定的时长内新传入的 action。即在指定的时间内,只执行一次 saga函数
      实例:
    
    import {put, call, takeEvery, select, fork, all, throttle } from 'redux-saga/effects';
    import axios from 'axios';
    
    
    const request = function(data) {
        return axios('/channel/listjson?pn=0&rn=30&tag1=明星&tag2=全部&ie=utf8', {
            params: data
        });
    }
    
    
    const getSagaImage = function* (data) {
        try {
            const res = yield call(request, data);
            const datas = res.data.data ?  res.data.data : null
            yield put({
                type: 'GET_IMAGE_SUCCESS',
                payload: datas
            });
            const uuid = yield select(state => state.user.username);
            console.log(uuid, 'uuid')
        } catch (err) {
            alert(err.message)
        }
    }
    
    
    
    const watchThrottle = function* () {
        yield throttle(3000, 'GET_IMAGES', getSagaImage);   // 3s内新的操作将被忽略
    }
    
    // const rootSaga =  function* () {
    //     yield takeEvery('GET_IMAGES', getSagaImage)
    // };
    
    const rootSaga = function* () {
        yield all([
            fork(watchThrottle),
        ])
    }
    
    export default rootSaga;
    


    链接:https://www.jianshu.com/p/6f96bdaaea22
     

    正因为当初对未来做了太多的憧憬,所以对现在的自己尤其失望。生命中曾经有过的所有灿烂,终究都需要用寂寞来偿还。
  • 相关阅读:
    JWT(json web token)--.JwtBearer jwt
    NET LOG日志的使用以及设置文件大小和数量限制
    Xshell、MobaXterm等5款主流SSH客户端对比
    RabbitMQ使用交换机处理异步消息队列------发布与订阅
    RabbitMQ使用交换机处理异步消息队列------分布式事务处理案例
    RabbitMQ使用交换机处理异步消息队列案例
    SqlServer Microsoft SQL Server 2005 使用复制(发布、订阅)的方式使主从数据库同步
    Hangfire-执行定时任务框架
    Catalina 默认使用zsh了,你可习惯
    你几点睡,就是什么命
  • 原文地址:https://www.cnblogs.com/candlia/p/11920050.html
Copyright © 2020-2023  润新知