zoukankan      html  css  js  c++  java
  • 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
     

    正因为当初对未来做了太多的憧憬,所以对现在的自己尤其失望。生命中曾经有过的所有灿烂,终究都需要用寂寞来偿还。
  • 相关阅读:
    【题解】【BT】【Leetcode】Populating Next Right Pointers in Each Node
    【题解】【BT】【Leetcode】Binary Tree Level Order Traversal
    【题解】【BST】【Leetcode】Unique Binary Search Trees
    【题解】【矩阵】【回溯】【Leetcode】Rotate Image
    【题解】【排列组合】【素数】【Leetcode】Unique Paths
    【题解】【矩阵】【回溯】【Leetcode】Unique Paths II
    【题解】【BST】【Leetcode】Validate Binary Search Tree
    【题解】【BST】【Leetcode】Convert Sorted Array to Binary Search Tree
    第 10 章 判断用户是否登录
    第 8 章 动态管理资源结合自定义登录页面
  • 原文地址:https://www.cnblogs.com/candlia/p/11920050.html
Copyright © 2011-2022 走看看