Redux-saga笔记

Redux-saga笔记

参考资料:

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

(1)redux的副作用处理

redux中的数据流大致是:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PFG9O7Id-1603866478530)(https://user-images.githubusercontent.com/17233651/42152085-b1c3f124-7e11-11e8-9ff1-654ef7b8b168.png)]

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

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

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ap48HzPK-1603866478532)(https://user-images.githubusercontent.com/17233651/42152877-0b8b08f8-7e14-11e8-9c77-719a67f40d46.png)]

在有副作用的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的缺点

thunk的缺点也是很明显的,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概述

redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,简单理解就是一个用于管理redux应用异步操作的中间件,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。

跟redux-thunk不同的是,redux-saga是控制执行的generator,在redux-saga中action是原始的js对象,把所有的异步副作用操作放在了saga函数里面。这样既统一了action的形式,又使得异步操作集中可以被集中处理。而且,redux-saga中使用声明式的Effect以及提供了更加细腻的控制流。

这意味着应用的逻辑会存在两个地方:

  1. reducer负责处理action的stage更新
  2. sagas负责协调那些复杂或者异步的操作

redux-saga特点:

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

安装

$ npm install --save redux-saga

3. 使用示例

假设我们有一个 UI 界面,在单击按钮时从远程服务器获取一些用户数据(为简单起见,我们只列出 action 触发代码)。

class UserComponent extends React.Component {
  ...
  onSomeButtonClicked() {
    const { userId, dispatch } = this.props
    dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
  }
  ...
}

这个组件 dispatch 一个 plain Object 的 action 到 Store。我们将创建一个 Saga 来监听所有的 USER_FETCH_REQUESTED action,并触发一个 API 调用获取用户数据。

sagas.js
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'

// worker Saga : 将在 USER_FETCH_REQUESTED action 被 dispatch 时调用
function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED", user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED", message: e.message});
   }
}

/*
  在每个 `USER_FETCH_REQUESTED` action 被 dispatch 时调用 fetchUser
  允许并发(译注:即同时处理多个相同的 action)
*/
function* mySaga() {
  yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}

/*
  也可以使用 takeLatest

  不允许并发,dispatch 一个 `USER_FETCH_REQUESTED` action 时,
  如果在这之前已经有一个 `USER_FETCH_REQUESTED` action 在处理中,
  那么处理中的 action 会被取消,只会执行当前的
*/
function* mySaga() {
  yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
}

export default mySaga;

为了能跑起 Saga,我们需要使用 redux-saga 中间件将 Saga 与 Redux Store 建立连接。

main.js
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

import reducer from './reducers'
import mySaga from './sagas'

// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

// then run the saga
sagaMiddleware.run(mySaga)

// render the application

redux-saga基本用法总结:

  • 使用 createSagaMiddleware 方法创建 saga 的 Middleware ,然后在创建的 redux 的 store 时,使用 applyMiddleware 函数将创建的 saga Middleware 实例绑定到 store 上,最后可以调用 saga Middleware 的 run 函数来执行某个或者某些 Middleware 。
  • 在 saga 的 Middleware 中,可以使用 takeEvery 或者 takeLatest 等 API 来监听某个 action ,当某个 action 触发后, saga 可以使用 call 发起异步操作,操作完成后使用 put 函数触发 action ,同步更新 state ,从而完成整个 State 的更新。

4. 声明式的Effect

redux-saga 的世界里,Sagas 都用 Generator 函数实现。我们从 Generator 里 yield 纯 JavaScript 对象以表达 Saga 逻辑。
我们称呼那些对象为 Effect。Effect 是一个简单的对象,这个对象包含了一些给 middleware 解释执行的信息。

你可以把 Effect 看作是发送给 middleware 的指令以执行某些操作(调用某些异步函数,发起一个 action 到 store,等等)。

你可以使用 redux-saga/effects 包里提供的函数来创建 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)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dsKKIIKJ-1603866478532)(https://user-images.githubusercontent.com/17233651/42163340-a719c8f2-7e34-11e8-9c62-c6270e5eb1cb.png)]

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mdc3345B-1603866478533)(https://user-images.githubusercontent.com/17233651/42163582-c92ae632-7e35-11e8-804b-5e6bb60921da.png)]

对比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')"
)

5. Effect提供的具体方法

引入:

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

1、Saga 辅助函数

redux-saga提供了一些辅助函数,用来在一些特定的action 被发起到Store时派生任务,下面先来讲解两个辅助函数:takeEvery 和 takeLatest。

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

takeEvery('login',loginFunc)

相当于

while(true){
  yield take('login');
  yield fork(loginFunc);
}

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

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

takeLatest('login',loginFunc)

与takeLatest不同的是,takeLatest是会监听执行最近的那个被触发的action。如果之前已经有一个任务在执行,那之前的这个任务会自动被取消。

2、Effect Creators

redux-saga框架提供了很多创建effect的函数,下面我们就来简单的介绍下开发中最常用的几种

  • take(pattern):在Store上等待指定action
  • put(action):向Store发送action
  • select(selector, …args):获取Store中的数据
  • call(fn, …args):函数调用
  • fork(fn, …args):和call类似,但是是非阻塞的,立即返回

如果你想要同时监听不同的action,可以使用all()或者race()把他们组合成一个root saga

export default function* rootSaga() {
  yield all([
    takeEvery("FOO_ACTION", fooASaga),
    takeEvery("BAR_ACTION", barASaga)
  ])
}
take(pattern)

take这个方法,是用来监听action,返回的是监听到的action对象。它创建了一个命令对象,告诉middleware等待一个特定的action, Generator会暂停,直到一个与pattern匹配的action被发起,才会继续执行下面的语句,也就是说,take是一个阻塞的 effect 。比如:

const loginAction = {
   type:'login'
}

在UI Component中dispatch一个action:

dispatch(loginAction)

在saga中使用:

const action = yield take('login');

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

{
  type:'login'
}
put(action)

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tzV337S0-1603866478534)(https://user-images.githubusercontent.com/17233651/42271232-68a4919c-7fb5-11e8-88d3-047f17404859.png)]

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

yield put({type:'login'})

put函数是用来发送action的 effect,你可以简单的把它理解成为redux框架中的dispatch函数,当put一个action后,reducer中就会计算新的state并返回,注意: put 也是阻塞 effect。

select(selector, …args)

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

const state= yield select()
call(fn, …args)

call函数你可以把它简单的理解为就是可以调用其他函数的函数,它命令 middleware 来调用fn 函数, args为函数的参数,注意: fn 函数可以是一个 Generator 函数,也可以是一个返回 Promise 的普通函数,call 函数也是阻塞 effect。

yield call(fetch,'/userInfo',username)
fork(fn, …args)

fork 函数和 call 函数很像,都是用来调用其他函数的,但是fork函数是非阻塞函数,也就是说,程序执行完 yield fork(fn, args) 这一行代码后,会立即接着执行下一行代码语句,而不会等待fn函数返回结果后,再执行下面的语句。

export default function* rootSaga() {
 // 下面的四个 Generator 函数会一次执行,不会阻塞执行
 yield fork(addItemFlow)
 yield fork(removeItemFlow)
 yield fork(toggleItemFlow)
 yield fork(modifyItem)
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值