Redux-saga-整理

mazy發表於2018-06-25

image


介紹

  1. 在redux中更好的解決非同步操作
  2. redux-saga相當於在redux原來的資料流中多了一層,對action進行監聽
  3. 接收到action時,派發一個任務維護state
  4. saga通過Generator方式建立,非同步方法同步化

正常redux流程

Redux-saga-整理

加入redux-saga之後的流程

Redux-saga-整理


使用方式

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
//引入saga檔案
import { rootSaga } from './rootSaga'
//使用 redux-saga 模組的 createSagaMiddleware 工廠函式來建立一個 Saga middleware。
//執行 rootSaga 之前,我們必須使用 applyMiddleware 將 middleware 連線至 Store。然後使用 

const sagaMiddleware = createSagaMiddleware();
const middlewares = [ sagaMiddleware ];

const store = createStore(rootReducer, applyMiddleware(...middlewares));
sagaMiddleware.run(rootSaga);
複製程式碼

redux-saga 輔助函式

sage提供了一些輔助函式,包裝了一些內部方法,用來在一些特定的action被髮起到store時派生任務

takeEvery

import { call, put } from 'redux-saga/effects'
export function* fetchData(action) {
   try {
      const data = yield call(Api.fetchUser, action.payload.url);
      yield put({type: "FETCH_SUCCEEDED", data});
   } catch (error) {
      yield put({type: "FETCH_FAILED", error});
   }
}

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

//有一種用法:監控所有的發起的action
yield takeEvery('*', fn)
複製程式碼

takeEvery 允許多個 fetchData 例項同時啟動, 在某個特定時刻, 儘管之前還有一個或多個fetchData尚未結束, 我們還是可以啟動一個新的fetchData任務-->意思就是隻用呼叫了 FETCH_REQUESTED action的時候就會啟動 fetchData 任務.

takeLatest

function* watchFetchData() {
  yield* takeLatest('FETCH_REQUESTED', fetchData)
}
複製程式碼

在任何時刻 takeLatest只允許一個 fetchData 任務在執行,並且這個任務是最後被啟動的那個,如果之前有一個任務再啟動的時候執行了fetchData , 那麼之前的任務會被自動取消 -- 可以獲得最後一次(最新)呼叫FETCH_REQUESTED action 得到的結果.


Effects

概念

sagas都是Generator函式實現,可以用yield 對 js 物件來表達saga的邏輯,這些物件就是effect,

  1. sagas都是用Generator函式實現的
  2. 在 Generator 函式中,yield 右邊的任何表示式都會被求值,結果會被 yield 給呼叫者
  3. 用yield對Effect(簡單物件),進行解釋執行
  4. Effect 是一個簡單的物件,這個物件包含了一些給 middleware 解釋執行的資訊。 你可以把 Effect 看作是傳送給 middleware 的指令以執行某些操作(呼叫某些非同步函式,發起一個 action 到 store,等等)
//官方例子
import { takeEvery } from 'redux-saga/effects'
import Api from './path/to/api'

//監聽如果有一個呼叫PRODUCTS_REQUESTED 的action的話,就會匹配到第二個引數所代表的effect
function* watchFetchProducts() {
  yield takeEvery('PRODUCTS_REQUESTED', fetchProducts)
}
//執行,獲取資料
//使用Generator 呼叫了Api.fetch,在Generator函式中,yield右面的任何表示式都會被求值,結果會被yield給呼叫者
function* fetchProducts() {
  const products = yield Api.fetch('/products')
  console.log(products)
}

//第二種方式
import { call } from 'redux-saga/effects'
//call(fn, ...args) 這個函式。與前面的例子不同的是,現在我們不立即執行非同步呼叫,相反,call
//建立了一條描述結果的資訊就像在 Redux 裡你使用 action 建立器,建立一個將被 Store 執行的、描述 action 的純文字物件。
//call 建立一個純文字物件描述函式呼叫。redux-saga middleware 確保執行函式呼叫並在響應被 resolve 時恢復 generator
function* fetchProducts() {
  const products = yield call(Api.fetch, '/products')
  // ...
}

複製程式碼

傳送action到store

//這種方式是Generator獲取到了返回值,在呼叫dispatch
function* fetchProducts(dispatch)
  const products = yield call(Api.fetch, '/products')
  dispatch({ type: 'PRODUCTS_RECEIVED', products })
}

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

複製程式碼

使用 try/catch 的方式捕獲saga的錯誤資訊


一些概念

從 Saga 內觸發非同步操作(Side Effect)總是由 yield 一些宣告式的 Effect 來完成的 , 一個 Saga 所做的實際上是組合那些所有的 Effect,共同實現所需的控制流。使用上是用yield Effects的方式來完成,Effect包括

  1. call: yield call(Generator, param) yield一個call,命令middleware以引數param呼叫函式 到Generator,saga等待Generator執行之後,接收返回值繼續執行,call是堵塞的,就是等待執行完再繼續執行,返回的是執行完正常返回的結果.
  2. take: 是阻塞的,只有監聽到他(action.type),才會繼續往下執行.也就是說建立一個effect的描述資訊,用來命令middleware在Store上等待指定action,在發起與他相匹配的action之前,Generator將暫停.
  3. put: 類似dispatch方法,觸發一個action,用來命令middleware向Store發起一個action請求,而且是非阻塞的
  4. fork: 非阻塞的,遇到它不需要等待他執行完畢,就可以繼續往下執行,fork返回的是一個任務,可以被取消
  5. cancel: 針對fork方法返回的任務,進行取消
  6. select: 可以從全域性state中獲取狀態
  7. saga: 就是用* 註冊的函式,一個函式就是一個saga
  8. effect: 上面的call,put,take...就是effect
function* watchAndLog() {
  while (true) {
    const action = yield take('*')
    const state = yield select()
  }
}
take,它將會暫停 Generator 直到一個匹配的 action 被髮起了,watchAndLog 處於暫停狀態,直到任意的一個 action 被髮起。

複製程式碼

無阻塞呼叫-fork

fork一個任務,任務會在後臺啟動,呼叫者也可以繼續它的流程,而不用等待被fork的任務執行結束 當我們需要有併發操作的時候,使用call effect會阻塞saga的執行,使用fork就需要關心被阻塞,或者等待結果返回在繼續執行

const result = yield fork (saga,param)
複製程式碼

同時執行多個任務

const [users, repos] = yield [
  call(fetch, '/users'),
  call(fetch, '/repos')
]
複製程式碼

當需要同步執行多個任務,需要把yield一個包含了effect的陣列,Generator將會阻塞,等所有的effect都執行完畢


使用

//redux.connect所需要繫結到props上的action
function mapDispatchToProps(dispatch) {
    return {
        getHome: bindActionCreators(getHomeAdData, dispatch)
    }
}
//一個 action creator
export function getHomeAdData(){
    return {
        type: actionTypes.HOME_AD_DATA,
    }
}
//監聽action.type,然後出發後面的action
export default function* rootSaga () {
    // 就在這個rootSaga裡面利用takeEvery去監聽action的type
    yield takeEvery('HOME_AD_DATA', getHomeAdData);
    yield takeEvery('GET_LIKE_LIST_DATA', getLikeListData);
}
//通過yield call Effect 獲取返回值,繼續下面操作
export function* getHomeAdData() {
    let data = yield call(getAdData)
    ...
    yield put({type:UPDATE_HOME_AD_DATA, data: dataArr})
}

export function getAdData() {
    const result = axios.get('/api/homead')
    return result
}
複製程式碼