Redux Saga 實(shí)踐

本文用以記錄從調(diào)研Redux Saga,到應(yīng)用到項(xiàng)目中的一些收獲。

什么是Redux Saga

官網(wǎng)解釋 來(lái)自:https://github.com/redux-saga/redux-saga

redux-saga is a library that aims to make side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) in React/Redux applications easier and better.

The mental model is that a saga is like a separate thread in your application that's solely responsible for side effects. redux-saga is a redux middleware, which means this thread can be started, paused and cancelled from the main application with normal redux actions, it has access to the full redux application state and it can dispatch redux actions as well.

剛開(kāi)始了解Saga時(shí),看官方解釋?zhuān)⒉皇呛芮宄降资鞘裁??Saga的副作用(side effects)到底是什么?

通讀了官方文檔后,大概了解到,副作用就是在action觸發(fā)reduser之后執(zhí)行的一些動(dòng)作, 這些動(dòng)作包括但不限于,連接網(wǎng)絡(luò),io讀寫(xiě),觸發(fā)其他action。并且,因?yàn)镾age的副作用是通過(guò)redux的action觸發(fā)的,每一個(gè)action,sage都會(huì)像reduser一樣接收到。并且通過(guò)觸發(fā)不同的action, 我們可以控制這些副作用的狀態(tài), 例如,啟動(dòng),停止,取消。

所以,我們可以理解為Sage是一個(gè)可以用來(lái)處理復(fù)雜的異步邏輯的模塊,并且由redux的action觸發(fā)。

使用Saga解決的問(wèn)題

最初,在開(kāi)始探究Saga之前,我們是希望尋求一種方式來(lái)隔離開(kāi)應(yīng)用前端的展現(xiàn)層,業(yè)務(wù)層數(shù)據(jù)層。 大概想法是使用react展現(xiàn)數(shù)據(jù),redux管理數(shù)據(jù),然后借助redux的middleware來(lái)實(shí)現(xiàn)業(yè)務(wù)層。這樣原有的react為核心的項(xiàng)目架構(gòu),變成了redux為核心的架構(gòu)。

在最初的調(diào)研中redux-thunk是首先考慮的,redux-thunk是在action作用到reducer之前觸發(fā)一些業(yè)務(wù)操作。剛好起到控制層的作用。

但是,馬上了解到了redux-sage,因?yàn)榇蠹叶荚趯?duì)比兩者。本文并不會(huì)做對(duì)比,在文章的最后會(huì)簡(jiǎn)單介紹為什么選了Saga而不是thunk的原因,僅供參考。

在瀏覽了很多比較文章后,最終,我們選擇了redux-saga來(lái)處理應(yīng)用的控制層。

下面是一個(gè)簡(jiǎn)單的例子:

在用戶(hù)提交表單的時(shí)候,我們想要做如下事情:

  • 校驗(yàn)一些輸入信息 (簡(jiǎn)單, 寫(xiě)在組件里)
  • 彈起提示信息(聰明的我,一定要寫(xiě)一個(gè)公用的提示信息模塊,這樣別的頁(yè)面引入就可以用了, 呵呵呵呵。。。)
  • 提交后端服務(wù) (直接組件里面fetch吧。。。)
  • 拿到后端返回狀態(tài) (promise so easy...)
  • 隱藏提示信息 (這個(gè)有點(diǎn)難度,不過(guò)難不倒我,我給組建加一個(gè)控制屬性)
  • 更新redux store (dispatch咯。。。)

好了,現(xiàn)在我們要把剛剛做的事情加到所有的表單上。。。 (WTF, 每個(gè)form組件都要做同樣的事情。。。頁(yè)面的代碼丑的不想再多看一眼。。。)

用了redux-saga之后:

  • form組件觸發(fā)提交action (一行簡(jiǎn)單的dispatch)
  • reducer這個(gè)action不需要我處理 (打醬油了)
  • saga提交表單的副作用走起~ (監(jiān)聽(tīng)到觸發(fā)副作用的action)
    • 校驗(yàn)一下
    • 通知顯示層彈起信息框 (dispatch一下變更控制信息框彈起的store)
    • 提交表單 (yield一個(gè)promis,yield是javascript generator的語(yǔ)法,稍后有介紹)
    • 拿到后端返回狀態(tài)
    • 更新redux store (dispatch一下)
redux-saga-01.jpg

可以看到在使用了Saga后,react只負(fù)責(zé)數(shù)據(jù)如何展示,redux來(lái)負(fù)責(zé)數(shù)據(jù)的狀態(tài)和綁定數(shù)據(jù)到react,而Saga處理了大部分復(fù)雜的業(yè)務(wù)邏輯。

通過(guò)這個(gè)改變,前端應(yīng)用的代碼結(jié)構(gòu)更加清晰,業(yè)務(wù)層可復(fù)用的部分增加。當(dāng)然,Saga對(duì)自動(dòng)化測(cè)試也支持的很好,可以將邏輯單獨(dú)使用自動(dòng)化腳本測(cè)試,提高項(xiàng)目質(zhì)量。

開(kāi)始前需要了解的幾個(gè)概念

redux中間件

redux中文文檔解釋如下:

如果你使用過(guò) Express 或者 Koa 等服務(wù)端框架, 那么應(yīng)該對(duì) middleware 的概念不會(huì)陌生。 在這類(lèi)框架中,middleware 是指可以被嵌入在框架接收請(qǐng)求到產(chǎn)生響應(yīng)過(guò)程之中的代碼。例如,Express 或者 Koa 的 middleware 可以完成添加 CORS headers、記錄日志、內(nèi)容壓縮等工作。middleware 最優(yōu)秀的特性就是可以被鏈?zhǔn)浇M合。你可以在一個(gè)項(xiàng)目中使用多個(gè)獨(dú)立的第三方 middleware。

相對(duì)于 Express 或者 Koa 的 middleware,Redux middleware 被用于解決不同的問(wèn)題,但其中的概念是類(lèi)似的。它提供的是位于 action 被發(fā)起之后,到達(dá) reducer 之前的擴(kuò)展點(diǎn)。 你可以利用 Redux middleware 來(lái)進(jìn)行日志記錄、創(chuàng)建崩潰報(bào)告、調(diào)用異步接口或者路由等等。

可以簡(jiǎn)單理解為,中間件是可以在action到達(dá)reducer之前做一些事情的層。(有意思的是,saga應(yīng)該是在reducer被觸發(fā)之后才觸發(fā)的。TODO, 需要進(jìn)一步驗(yàn)證)

Javascript Generator

在使用Saga之前,建議先了解Javascript生成器,因?yàn)镾aga的副作用都是通過(guò)生成器來(lái)實(shí)現(xiàn)的。

可以在阮一峰的ECMAScript 6 入門(mén): Generator 函數(shù)的語(yǔ)法Generator 函數(shù)的異步應(yīng)用章節(jié)中了解更多細(xì)節(jié)。

如何使用

redux-sage官方文檔有很詳細(xì)的使用說(shuō)明,這里只做簡(jiǎn)單的上手說(shuō)明。

安裝redux-sage

npm install --save redux-saga

給redux添加中間件

在定義生成store的地方,引入并加入redux-sage中間件。

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)

副作用

副作用,顧名思義,在主要作用(action觸發(fā)reducer)之外,用來(lái)處理其他業(yè)務(wù)邏輯。redux-saga提供了幾種產(chǎn)生副作用的方式, 主要用到了有兩種takeEverytakeLates。

takeEvery會(huì)在接到相應(yīng)的action之后不斷產(chǎn)生新的副作用。 比如,做一個(gè)計(jì)數(shù)器按鈕,用戶(hù)需要不斷的點(diǎn)擊按鈕,對(duì)后臺(tái)數(shù)據(jù)更新,這里可以使用takeEvery來(lái)觸發(fā)。

takeLatest在相同的action被觸發(fā)多次的時(shí)候,之前的副作用如果沒(méi)有執(zhí)行完,會(huì)被取消掉,只有最后一次action觸發(fā)的副作用可以執(zhí)行完。比如,我們需要一個(gè)刷新按鈕, 讓用戶(hù)可以手動(dòng)的從后臺(tái)刷新數(shù)據(jù), 當(dāng)用戶(hù)不停單機(jī)刷新的時(shí)候, 應(yīng)該最新一次的請(qǐng)求數(shù)據(jù)被刷新在頁(yè)面上,這里可以使用takeLatest。

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

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

注意,takeEvery第一個(gè)參數(shù)可以是數(shù)組或者方法。 也可以有第三個(gè)參數(shù)用來(lái)傳遞變量給方法。

call方法

call有些類(lèi)似Javascript中的call函數(shù), 不同的是它可以接受一個(gè)返回promise的函數(shù),使用生成器的方式來(lái)把異步變同步。

put方法

put就是redux的dispatch,用來(lái)觸發(fā)reducer更新store

有什么弊端

目前在項(xiàng)目實(shí)踐中遇到的一些問(wèn)題:

  • redux-saga模型的理解和學(xué)習(xí)需要投入很多精力
  • 因?yàn)樾枰胊ction觸發(fā),所以會(huì)產(chǎn)生很多對(duì)于reducer無(wú)用的action, 但是reducer一樣會(huì)跑一輪,雖然目前沒(méi)有觀測(cè)到性能下降,但還是有計(jì)算開(kāi)銷(xiāo)
  • 在action的定義上要謹(jǐn)慎,避免action在saga和reducer之間重復(fù)觸發(fā),造成死循環(huán)

后記

總體而言,對(duì)于redux-saga的第一次嘗試還是很滿(mǎn)意的。 在業(yè)務(wù)邏輯層,可以簡(jiǎn)化代碼,使代碼更加容易閱讀。 在重用方面,解耦顯示層和業(yè)務(wù)層之后, 代碼的重用度也得到了提升。

選擇Saga的原因

開(kāi)始的時(shí)候一直在猶豫是否需要使用Saga或thunk,因?yàn)椴⒉荒芎芎玫陌盐者@兩者到底解決了什么問(wèn)題。之后,在瀏覽文章的時(shí)候看到了一遍對(duì)比兩者的長(zhǎng)文,列出了不少開(kāi)發(fā)者對(duì)兩者的擔(dān)憂(yōu)和爭(zhēng)論,其中不乏閃光的觀點(diǎn),長(zhǎng)文的最后作者寫(xiě)到:“不管是否用得上,你都應(yīng)該嘗試一下”。 這句話(huà)使我決定了嘗試用saga或thunk來(lái)實(shí)踐把前端分層的設(shè)想。

之所以最后選擇了saga是因?yàn)檫@段 Cheng Lou 的視頻:
On the Spectrum of Abstraction (youtube)

視頻中講述了在一種抽象的概念下如何去選擇一種技術(shù)。 其中一個(gè)理論是:越是用來(lái)解決具體問(wèn)題的技術(shù),使用起來(lái)越容易,越高效,學(xué)習(xí)成本越低;越是用來(lái)解決寬泛?jiǎn)栴}的技術(shù),使用起來(lái)越難,學(xué)習(xí)成本越高。 thunk解決的是很具體的一個(gè)問(wèn)題,就是在action到達(dá)reducer之前做一些其他的業(yè)務(wù),比如fetch后端, 它在做這件事的上很高效。而Saga解決的問(wèn)題要更寬泛一些,因?yàn)閟aga只是攔截了action,至于做什么,開(kāi)發(fā)者需要自己來(lái)考慮,可以是fetch后端,也可以是更新redux store, 甚至可以執(zhí)行action帶進(jìn)來(lái)的callback。 很顯然對(duì)于一個(gè)業(yè)務(wù)層來(lái)說(shuō),saga會(huì)是一個(gè)更合適的選擇,但同時(shí)也帶來(lái)了學(xué)習(xí)成本的提高。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容