Redux-saga學習筆記

概述javascript

Redux-saga在Redux應用中扮演’中間件’的角色,主要用來執行數據流中的異步操做。主要經過ES6中的generator函數和yield關鍵字來以同步的方式實現異步操做。java

基本用法:redux

  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的更新。

 

APIapi

  1. takeEvery

     用來監聽action,每一個action都觸發一次,若是其對應是異步操做的話,每次都發起異步請求,而不論上次的請求是否返回。promise

1
2
3
4
5
import  { takeEvery } from  'redux-saga/effects'
 
function * watchFetchData() {
   yield  takeEvery( 'FETCH_REQUESTED' , fetchData)
}

  

  1. takeLatest

    做用同takeEvery同樣,惟一的區別是它只關注最後,也就是最近一次發起的異步請求,若是上次請求還未返回,則會被取消。緩存

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

  

  1. call

   call用來調用異步函數,將異步函數和函數參數做爲call函數的參數傳入,返回一個js對象。saga引入他的主要做用是方便測試,同時也能讓咱們的代碼更加規範化。app

同js原生的call同樣,call函數也能夠指定this對象,只要把this對象當第一個參數傳入call方法就行了異步

saga一樣提供apply函數,做用同call同樣,參數形式同js原生apply方法。 函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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。

這個藉口主要也是爲了方便咱們寫單元測試提供的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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方法,將錯誤拋出;另外一種是使用變量緩存成功失敗的狀態。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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。

1
2
3
4
5
6
7
8
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啓動一個任務時,該任務在後臺繼續執行,從而使得咱們的執行流能繼續往下執行而沒必要必定要等待返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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操做執行完畢。

1
2
3
4
5
6
7
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操做接受正常返回的請求,而且將剩餘的請求取消。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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的任務流程處理完成後,才但是執行下一個任務流。
  代碼參考:

    

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

用來防止接二連三的響應某個事件。    

1
2
3
4
5
6
7
8
9
import  { throttle } from  'redux-saga/effects'
 
function * handleInput(input) {
   // ...
}
 
function * watchInput() {
   yield  throttle(500,  'INPUT_CHANGED' , handleInput)
}

  

  • Debouncing

延時執行,使用delay函數實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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/

相關文章
相關標籤/搜索