Redux-saga学习笔记

概述

Redux-saga在Redux应用中扮演’中间件’的角色,主要用来执行数据流中的异步操作。主要通过ES6中的generator函数和yield关键字来以同步的方式实现异步操作。

基本用法:

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

API

  1. takeEvery

用来监听action,每个action都触发一次,如果其对应是异步操作的话,每次都发起异步请求,而不论上次的请求是否返回。

import { takeEvery } from 'redux-saga/effects'

function* watchFetchData() {
yield takeEvery('FETCH_REQUESTED', fetchData)
}

  

  1. takeLatest

作用同takeEvery一样,唯一的区别是它只关注最后,也就是最近一次发起的异步请求,如果上次请求还未返回,则会被取消。

function* watchFetchData() {
yield takeLatest('FETCH_REQUESTED', fetchData)
}

  

  1. call

call用来调用异步函数,将异步函数和函数参数作为call函数的参数传入,返回一个js对象。saga引入他的主要作用是方便测试,同时也能让我们的代码更加规范化。

同js原生的call一样,call函数也可以指定this对象,只要把this对象当第一个参数传入call方法就好了

saga同样提供apply函数,作用同call一样,参数形式同js原生apply方法。

import { call } from 'redux-saga/effects'

function* fetchProducts() {
const products = yield call(Api.fetch, '/products')
// ...
} 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')"
) yield call([obj, obj.method], arg1, arg2, ...)
yield apply(obj, obj.method, [arg1, arg2, ...])

  

  1. cps

同call方法基本一样,但是用处不太一样,call一般用来完成异步操作,cps可以用来完成耗时比较长的io操作等。

  1. put

put是saga对Redux中dispatch方法的一个封装,调用put方法后,saga内部会分发action通知Store更新state。

这个借口主要也是为了方便我们写单元测试提供的。

import { call, put } from 'redux-saga/effects'
// ... function* fetchProducts() {
const products = yield call(Api.fetch, '/products')
// create and yield a dispatch Effect
yield put({ type: 'PRODUCTS_RECEIVED', products })
} const products = {} // expects a dispatch instruction
assert.deepEqual(
iterator.next(products).value,
put({ type: 'PRODUCTS_RECEIVED', products }),
"fetchProducts should yield an Effect put({ type: 'PRODUCTS_RECEIVED', products })"
)

  

  1. 请求失败

有两种方式来处理请求失败的情况,一种是使用try-catch方法,将错误抛出;另一种是使用变量缓存成功失败的状态。

import Api from './path/to/api'
import { call, put } from 'redux-saga/effects' // ... function* fetchProducts() {
try {
const products = yield call(Api.fetch, '/products')
yield put({ type: 'PRODUCTS_RECEIVED', products })
}
catch(error) {
yield put({ type: 'PRODUCTS_REQUEST_FAILED', error })
}
} import Api from './path/to/api'
import { call, put } from 'redux-saga/effects' function fetchProductsApi() {
return Api.fetch('/products')
.then(response => ({ response }))
.catch(error => ({ error }))
} function* fetchProducts() {
const { response, error } = yield call(fetchProductsApi)
if (response)
yield put({ type: 'PRODUCTS_RECEIVED', products: response })
else
yield put({ type: 'PRODUCTS_REQUEST_FAILED', error })
}

  

  1. take

take的表现同takeEvery一样,都是监听某个action,但与takeEvery不同的是,他不是每次action触发的时候都相应,而只是在执行顺序执行到take语句时才会相应action。

当在genetator中使用take语句等待action时,generator被阻塞,等待action被分发,然后继续往下执行。

takeEvery只是监听每个action,然后执行处理函数。对于何时相应action和 如何相应action,takeEvery并没有控制权。

而take则不一样,我们可以在generator函数中决定何时相应一个action,以及一个action被触发后做什么操作。

最大区别:take只有在执行流达到时才会响应对应的action,而takeEvery则一经注册,都会响应action。

import { take, put } from 'redux-saga/effects'

function* watchFirstThreeTodosCreation() {
for (let i = 0; i < 3; i++) {
const action = yield take('TODO_CREATED')
}
yield put({type: 'SHOW_CONGRATULATION'})
}

  

  1. fork

非阻塞任务调用机制:上面我们介绍过call可以用来发起异步操作,但是相对于generator函数来说,call操作是阻塞的,只有等promise回来后才能继续执行,而fork是非阻塞的 ,当调用fork启动一个任务时,该任务在后台继续执行,从而使得我们的执行流能继续往下执行而不必一定要等待返回。

import { take, call, put, cancelled } from 'redux-saga/effects'
import Api from '...' function* authorize(user, password) {
try {
const token = yield call(Api.authorize, user, password)
yield put({type: 'LOGIN_SUCCESS', token})
yield call(Api.storeItem, {token})
return token
} catch(error) {
yield put({type: 'LOGIN_ERROR', error})
} finally {
if (yield cancelled()) {
// ... put special cancellation handling code here
}
}
}

  

  1. cancel

cancel的作用是用来取消一个还未返回的fork任务。防止fork的任务等待时间太长或者其他逻辑错误。

  • all

   all提供了一种并行执行异步请求的方式。之前介绍过执行异步请求的api中,大都是阻塞执行,只有当一个call操作放回后,才能执行下一个call操作, call提供了一种类似Promise中的all操作,可以将多个异步操作作为参数参入all函数中,如果有一个call操作失败或者所有call操作都成功返回,则本次all操作执行完毕。

import { all, call } from 'redux-saga/effects'

// correct, effects will get executed in parallel
const [users, repos] = yield all([
call(fetch, '/users'),
call(fetch, '/repos')
])

  

  • race

   有时候当我们并行的发起多个异步操作时,我们并不一定需要等待所有操作完成,而只需要有一个操作完成就可以继续执行流。这就是race借口的用处。他可以并行的启动多个异步请求,只要有一个 请求返回(resolved或者reject),race操作接受正常返回的请求,并且将剩余的请求取消。

import { race, take, put } from 'redux-saga/effects'

function* backgroundTask() {
while (true) { ... }
} function* watchStartBackgroundTask() {
while (true) {
yield take('START_BACKGROUND_TASK')
yield race({
task: call(backgroundTask),
cancel: take('CANCEL_TASK')
})
}
}

  

  • actionChannel  

  在之前的操作中,所有的action分发是顺序的,但是对action的响应是由异步任务来完成,也即是说对action的处理是无序的。
  如果需要对action的有序处理的话,可以使用actionChannel函数来创建一个action的缓存队列,但一个action的任务流程处理完成后,才可是执行下一个任务流。
  代码参考:

    

import { take, actionChannel, call, ... } from 'redux-saga/effects'

function* watchRequests() {
// 1- Create a channel for request actions
const requestChan = yield actionChannel('REQUEST')
while (true) {
// 2- take from the channel
const {payload} = yield take(requestChan)
// 3- Note that we're using a blocking call
yield call(handleRequest, payload)
}
} function* handleRequest(payload) { ... }
  •   eventChannel
  • Throttling

用来防止连续不断的响应某个事件。    

import { throttle } from 'redux-saga/effects'

function* handleInput(input) {
// ...
} function* watchInput() {
yield throttle(500, 'INPUT_CHANGED', handleInput)
}

  

  • Debouncing

延时执行,使用delay函数实现

import { delay } from 'redux-saga'

function* handleInput(input) {
// debounce by 500ms
yield call(delay, 500)
...
} function* watchInput() {
let task
while (true) {
const { input } = yield take('INPUT_CHANGED')
if (task) {
yield cancel(task)
}
task = yield fork(handleInput, input)
}
} const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))

  参考:https://redux-saga.js.org/docs/api/

Redux-saga的更多相关文章

  1. [React] 14 - Redux: Redux Saga

    Ref: Build Real App with React #14: Redux Saga Ref: 聊一聊 redux 异步流之 redux-saga  [入门] Ref: 从redux-thun ...

  2. react系列(六)Redux Saga

    在Redux中常要管理异步操作,目前社区流行的有Redux-Saga.Redux-thunk等.在管理复杂应用时,推荐使用Redux-Saga,它提供了用 generator 书写类同步代码的能力. ...

  3. react native redux saga增加日志功能

    redux-logger地址:https://github.com/evgenyrodionov/redux-logger 目前Reac native项目中已经使用redux功能,异步中间件使用red ...

  4. redux+saga+reducer

    saga.js这个文件里面的函数实际没有在其他jsx中引用吧?这个文件的作用就是把异步数据拿到,放进reducer,如果jsx想取,需要结合connect来取数据.

  5. redux saga学习

    来源地址:https://www.youtube.com/watch?v=o3A9EvMspig Saga的基本写法 takeEvery与takeLatest的区别 takeEvery是指响应每一个请 ...

  6. [转] How to dispatch a Redux action with a timeout?

    How to dispatch a Redux action with a timeout? Q I have an action that updates notification state of ...

  7. [Full-stack] 状态管理技巧 - Redux

    资源一: In React JS Tutorials, lectures from 9. From: React高级篇(一)从Flux到Redux,react-redux 从Flux到Redux,再到 ...

  8. Redux生态系统

    生态系统 Redux 是一个体小精悍的库,但它相关的内容和 API 都是精挑细选的,足以衍生出丰富的工具集和可扩展的生态系统. 如果需要关于 Redux 所有内容的列表,推荐移步至 Awesome R ...

  9. Redux异步解决方案之Redux-Thunk原理及源码解析

    前段时间,我们写了一篇Redux源码分析的文章,也分析了跟React连接的库React-Redux的源码实现.但是在Redux的生态中还有一个很重要的部分没有涉及到,那就是Redux的异步解决方案.本 ...

  10. [Code::Blocks] Install wxWidgets & openCV

    The open source, cross platform, free C++ IDE. Code::Blocks is a free C++ IDE built to meet the most ...

随机推荐

  1. Android 中基于 Binder的进程间通信

    摘要:对  Binder 工作机制进行了分析. 首先简述 Android 中 Binder 机制与传统的 Linux 进程间的通信比较,接着对基于 Binder 进程间通信的过程分析 最后结合开发实例 ...

  2. WEB开发性能优化--核心定义介绍篇(1)

    推荐理由 随着 互联网的蓬勃发展,并且伴随着产品功能的越来越复杂,对于技术人员来说最大的挑战就是如何在保证业务快速发展的同时,也可保证不断复杂的业务对用户体验的影响,其中对用户来说最重要的体验指标是如 ...

  3. js华氏度转为摄氏度

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. response.getWriter().write()与out.print()的区别(转)

    1.首先介绍write()和print()方法的区别:  (1).write():仅支持输出字符类型数据,字符.字符数组.字符串等  (2).print():可以将各种类型(包括Object)的数据通 ...

  5. Linux线程的创建

    一.线程与进程的区别 1.线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源. 2.进程是资源分配的基本单位.所有与该进程有关的资源,都 ...

  6. Framework7首页隐藏navbar其他页面显示navbar

    Framework7首页隐藏navbar其他页面显示navbar 帮别人解决问题,自己也记录一下, 首页.navbar加.navbar-hidden, 首页.page加.no-navbar, 如果首页 ...

  7. 重写NSArray与NSDictionary的descriptionWithLocale方法

    重写NSArray的descriptionWithLocale方法: NSArray+log.h #import <Foundation/Foundation.h> @interface ...

  8. Springboot在IDEA中执行,开启热部署

    仅适用IDEA中,eclipse中不需要设置 一.开启idea自动make功能 1 - Enable Automake from the compiler PRESS: CTRL + SHIFT + ...

  9. 应用程序写Xml文档

    主要用到CreateElement.CreateTextNode.CreateComment.AppendChild.InsertAfter方法 代码如下: XmlDocument document ...

  10. JS滚动加载

    var one = true;//设置一个全局变量 $(window).scroll(function () { var hight = document.body.scrollHeight - do ...