redux-saga學習筆記

小小冰發表於2018-04-22

在學習redux-saga之前,我們必須瞭解ES6 Generators [ES6 Generators]

介紹 

中介軟體:將具體業務和底層邏輯解耦的元件。redux中,中介軟體就是一個函式,對store.dispatch方法進行了改造,在發出 Action 和執行 Reducer 這兩步之間,新增了其他功能。 

附上官網redux-中介軟體介紹

redux應用中最常用的兩種非同步流處理方式: redux-thunk,redux-saga

redux-saga 是一個用於管理 Redux 應用非同步操作(Side Effects。譯註:直譯成 “副作用” 不太通順,所以這裡譯為 “非同步操作” 更好理解)的中介軟體(又稱非同步 action)。 redux-saga 通過建立 Sagas 將所有的非同步操作邏輯收集在一個地方集中處理,可以用來代替 redux-thunk 中介軟體。

它具有如下特性:

  • 集中處理 redux 副作用問題。

  • 被實現為 generator 。

  • 類 redux-thunk 中介軟體。

  • watch/worker(監聽->執行) 的工作形式。

如何使用 

  • 建立與執行
  • 測試(遍歷 generator 並對它的值(gen.next() // => { done: ..., value: ... } )做 deepEqual 測試)

redux-saga [官方文件]有詳細的使用說明 

redux-saga [中文文件]

概念 

redux-saga學習筆記


使用 Saga 輔助函式 

(常用)

  • Middleware API
    • createSagaMiddleware(...sagas)
    • middleware.run(saga, ...args)
  • Saga Helpers

    • takeEvery(pattern, saga, ...args) :接受Action,並且觸發某個方法(一個 Generator function) 例項同時啟動
    • takeLatest(pattern, saga, ..args) :多次觸發,去最後一次
  • Effect creators

    • take(pattern) :建立一條 Effect 描述資訊,指示 middleware 等待 Store 上指定的 action。 Generator 會暫停,直到一個與 pattern 匹配的 action 被髮起。
    • put(action) : put(action) 用於觸發 action,功能上類似於dispatch。
    • fork(fn, ...args) :建立一條 Effect 描述資訊,指示 middleware 以 無阻塞呼叫 方式執行 fn。fork 類似於 call,可以用來呼叫普通函式和 Generator 函式。但 fork 的呼叫是無阻塞的,在等待 fn 返回結果時,middleware 不會暫停 Generator。 相反,一旦 fn 被呼叫,Generator 立即恢復執行。 fork 與 race 類似,是一箇中心化的 Effect,管理 Sagas 間的併發。
    • call(fn, ...args) :cal阻塞型呼叫,有阻塞地呼叫 saga 或者返回 promise 的函式。
    • join(task): 建立一個效果描述,指示中介軟體等待先前分叉的任務的結果。
    • cancel(task): 是一個無阻塞 Effect。也就是說,Generator 將在取消異常被丟擲後立即恢復。
    • select(selector, ...args) : 作用和 redux thunk 中的 getState 相同。

      const id = yield select(state => state.id);複製程式碼

  • Effect combinators

    • race(effects) :rece Effect提供了一個方法,在多個Effects之間觸發一個競賽。在race Effect中,所有參與競賽的任務,除了優勝者,其他任務都會被取消
  • Interfaces

    • Task :Task 介面指定了通過 forkmiddleware.runrunSaga 執行 Saga 的結果。

redux-saga API詳解可以檢視 [API 參考] 

宣告式 Effects

Effect 是一個簡單的物件,該物件包含了一些給 middleware 解釋執行的資訊。可以通過使用 effects API 如 fork,call,take,put,cancel 等來建立 Effect。( redux-saga [API 參考]

觸發一個action 到 reducer的過程中,如 yield call(delay, 1000)即 yield 了下面的物件,call 建立了一條描述結果的資訊,然後,redux-saga middleware 將確保執行這些指令並將指令的結果返回給 Generator 

從 Saga 內觸發非同步操作(Side Effect)總是由 yield 一些宣告式的 Effect 來完成的 (你也可以直接 yield Promise,但是這會讓測試變得困難。使用 Effect 諸如 call 和 put,與高階 API 如 takeEvery 相結合,又有額外的易於測試的好處。

發起action

 redux-saga 提供了另外一個函式 put,這個函式用於建立 dispatch Effect

  •  yield put({ type: '',... }) 

yield關鍵字用來暫停和恢復一個生成器函式,yield關鍵字使生成器函式執行暫停,yield關鍵字後面的表示式的值返回給生成器的呼叫者。它可以被認為是一個基於生成器的版本的return關鍵字。

yield關鍵字實際返回一個IteratorResult物件,它有兩個屬性,value和done。value屬性是對yield表示式求值的結果,而done是false,表示生成器函式尚未完全完成。

錯誤處理 

try/catch 語法在 Saga 中捕獲錯誤 

redux-saga 是如何工作的? 

Redux Saga可以理解為一個和系統互動的常駐程式,其中,Saga可簡單定義如下: 

  • Saga = Worker + Watcher

Sagas通過Generator函式來建立,採用 Generator 函式來 yield Effects,只會在應用啟動時呼叫,我們可以把它看作是在後臺執行的程式,Sagas監聽發起的action,然後決定基於這個action來做什麼,是發起非同步呼叫還是發起其他的action到Store,甚至是呼叫其他的Sagas

  •  sagas 包含3個部分,用於聯合執行任務: 

Watcher/Worker 指的是一種使用兩個單獨的 Saga 來組織控制流的方式。

 1. root saga:立即啟動 sagas 的唯一入口 

 2. Watcher: 監聽發起的 action 並在每次接收到 action 時 fork 一個 worker。

 3. Worker: 處理 action 並結束它。

 eg: 

//動態執行 rootSaga。用於 applyMiddleware 階段之後執行 rootSaga。
middleware.run(rootSaga, ...args)
...

//Watcher/Worker

function* watcher() {
  while(true) {
    const action = yield take(ACTION)
    yield fork(worker, action.payload)
  }
}

function* worker(payload) {
  // ... do some stuff
}
複製程式碼

VS redux-thunk 

  • 使用redux-thunk

        try {
            dispatch({ type: LOGIN_REQUEST });
            let { data } = await request.post('/login', { user, password });
            await dispatch(loadUserData(data.uid));
            dispatch({ type: LOGIN_SUCCESS, data });
        } catch(error) {
            dispatch({ type: LOGIN_ERROR, error });
        }
    }
    export const loadUserData = (uid) => async (dispatch) => {
        try {
            dispatch({ type: USERDATA_REQUEST });
            let { data } = await request.get(`/users/${uid}`);
            dispatch({ type: USERDATA_SUCCESS, data });
        } catch(error) {
            dispatch({ type: USERDATA_ERROR, error });
        }
    }
    複製程式碼

  • 使用redux-saga

    export function* loginSaga() {
      while(true) {
        const { user, pass } = yield take(LOGIN_REQUEST) //等待 Store 上指定的 action LOGIN_REQUEST
        try {
          let { data } = yield call(request.post, '/login', { user, pass }); //阻塞,請求後臺資料
          yield fork(loadUserData, data.uid); //非阻塞執行loadUserData
          yield put({ type: LOGIN_SUCCESS, data }); //發起一個action,類似於dispatch
        } catch(error) {
          yield put({ type: LOGIN_ERROR, error });
        }  
      }
    }
    
    export function* loadUserData(uid) {
      try {
        yield put({ type: USERDATA_REQUEST });
        let { data } = yield call(request.get, `/users/${uid}`);
        yield put({ type: USERDATA_SUCCESS, data });
      } catch(error) {
        yield put({ type: USERDATA_ERROR, error });
      }
    }
    複製程式碼

  • 相比Redux Thunk,使用Redux Saga有幾處明顯的變化: 

1. 在元件中,不再dispatch(action creator),而是dispatch(pure action) 

2. 元件中不再關注由誰來處理當前action,action經由root saga分發 

3. 具體業務處理方法中,通過提供的call/put等幫助方法,宣告式的進行方法呼叫 

4. 使用ES6 Generator語法,簡化非同步程式碼語法 5. redux-saga 將非同步任務進行了集中處理,且方便測試。

結論 

redux-saga的使用更多的是根據個人需求與習慣

redux-saga 的優點: 

1. 宣告式 Effects:所有的操作以JavaScript物件的方式被 yield,並被 middleware 執行。使得在 saga 內部測試變得更加容易,可以通過簡單地遍歷 Generator 並在 yield 後的成功值上面做一個 deepEqual 測試。

 2. 高階的非同步控制流以及併發管理:可以使用簡單的同步方式描述非同步流,並通過 fork 實現併發任務。 

3. 架構上的優勢:將所有的非同步流程控制都移入到了 sagas,UI 元件不用執行業務邏輯,只需 dispatch action 就行,增強元件複用性。 

目前在專案實踐中遇到的一些問題:

 1. redux-saga 不強迫我們捕獲異常,這往往會造成異常發生時難以發現原因。因此,一個良好的習慣是,相信任何一個過程都有可能發生異常。 

2. generator 的除錯環境比較糟糕,babel 的 source-map 經常錯位,經常要手動加 debugger 來除錯。 

4. 在action的定義上要謹慎,避免action在saga和reducer之間重複觸發,造成死迴圈