Redux-saga


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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM