redux-saga入門


redux-sage和redux-thunk類似都是redux的中間件,都用於處理異步操作。redux-saga使用ES6的Generator功能,避免了redux-thunk的回調寫法,並且便於測試。

下面展示了最簡單是使用示例

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;
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

put等一些方法是saga提供的指令,返回一個Effect,Effect是一個簡單的js對象,包含了要被sgag middleware執行的指令,當middleware拿到一個被saga yield的Effect,它會暫停saga,直到Effect執行完成,然后saga回恢復執行。

redux-saga提供一些輔助函數,用於在action被發起到store是派生人物。

takeEvery()可以在某個action發起是啟動一個任務

import { takeEvery } from 'redux-saga'

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

takeLatest()和takeEvent()功能類似,不同的是takeEvent()允許同時啟動多個任務,即使已啟動的任務未結束,也會啟動一個新的任務。takeLatest()同時只允許啟動一個任務,一個新的任務啟動,之前的任務即使未完成也會被取消。

如果有多個sage監聽不同的action

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

// FETCH_USERS
function* fetchUsers(action) { ... }

// CREATE_USER
function* createUser(action) { ... }

// 同時使用它們
export default function* rootSaga() {
  yield takeEvery('FETCH_USERS', fetchUsers)
  yield takeEvery('CREATE_USER', createUser)
}

在 Generator 函數中,yield 右邊的任何表達式都會被求值,結果會被 yield 給調用者。

 

測試時我我們不可能真正的執行任務發到服務器,我們需要檢測的只是yield后面語句調用(通常是調用參數的檢查)或值是否正確。但當yield 一個返回值是Promise的方法時,就沒辦法比較了,所以當yield一個異步api時使用saga提供的call()方法代替異步api的直接調用,call()返回一個普通的js對象,所以在測試時可以很容易檢測。

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

當dispatch一個action時,也同樣難以測試,所以saga提供了put()方法來代替直接調用dispatch。

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

function* fetchProducts() {
  const products = yield call(Api.fetch, '/products')
  // 創建並 yield 一個 dispatch Effect
  yield put({ type: 'PRODUCTS_RECEIVED', products })
}
import { call, put } from 'redux-saga/effects'
import Api from '...'

const iterator = fetchProducts()

// 期望一個 call 指令
assert.deepEqual(
  iterator.next().value,
  call(Api.fetch, '/products'),
  "fetchProducts should yield an Effect call(Api.fetch, './products')"
)

// 創建一個假的響應對象
const products = {}

// 期望一個 dispatch 指令
assert.deepEqual(
  iterator.next(products).value,
  put({ type: 'PRODUCTS_RECEIVED', products }),
  "fetchProducts should yield an Effect put({ type: 'PRODUCTS_RECEIVED', products })"
)

錯誤處理

使用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 })
  }
}

在測試時使用throw來拋出一個錯誤

import { call, put } from 'redux-saga/effects'
import Api from '...'

const iterator = fetchProducts()

// 期望一個 call 指令
assert.deepEqual(
  iterator.next().value,
  call(Api.fetch, '/products'),
  "fetchProducts should yield an Effect call(Api.fetch, './products')"
)

// 創建一個模擬的 error 對象
const error = {}

// 期望一個 dispatch 指令
assert.deepEqual(
  iterator.throw(error).value,
  put({ type: 'PRODUCTS_REQUEST_FAILED', error }),
  "fetchProducts should yield an Effect put({ type: 'PRODUCTS_REQUEST_FAILED', error })"
)

 


免責聲明!

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



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