redux-saga 實踐總結

blank

redux-saga 實踐總結

有關redux-saga 的文章,網絡上早已是汗牛充棟。因此,本篇主要談一談自己的理解,以及實踐中的經驗總結。

眾所周知,redux大部分的想法,都來自於elm 。在elm 和redux 中,整個應用就是一個純函數。 elm 通過在reducer 中返回一些聲明副作用的task 來處理異步問題,而redux 借鑒koa 的插件機制,用中間件改造dispatch ,從而誕生了一批通過構造滿足特殊pattern 條件的action 來解決副作用的問題。

而redux-saga 獨闢蹊徑,監聽action 來執行有副作用的task,以保持action 的簡潔性。並且引入了sagas的機制和generator的特性,讓redux-saga非常方便地處理複雜異步問題。

有意思的是,redux 借鑒了elm,但在處理異步問題(副作用問題在前端一般為異步問題)上,借鑒了koa 中間件的形式,而redux-saga 卻又去從elm 取經,借鑒了獨立task 的形式。但是說到底,redux-saga 是一個redux 的中間件。這個故事告訴我們,有好的設計不如有強大的擴展性。

redux-saga 本身也有良好的擴展性。比如,易證得,但凡redux 中間件,都可以用redux-saga 來重寫。當然了,不是說用了redux-saga,其它異步中間件就不能用了,只是說不能保證redux-saga 能恰好和你之前使用的中間件配合良好。

redux-saga 簡介

redux-saga 是一個redux 中間件,它具有如下特性:

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

  • 被實現為generator 。

  • 類redux-thunk 中間件。

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

讀者也可以從這裡查看官方定義

對於剛接觸redux-saga 的同學,可以先來一段簡單的代碼快速了解redux-saga 諸多特性。

//類thunk的worker “進程”function*load(){yieldput({type:BEGIN_LOAD_DATA});try{constresult=yieldcall(fetch,UrlMap.loadData);yieldput({type:LOAD_DATA_SUCCESS,payload:result,});}catch(e){yieldput({type:LOAD_DATA_ERROR,payload:e,error:true,});}}function*saga(){//創建一個監聽“進程”yieldfork(watch(CLICK_LOAD_BUTTON,load))}

Effects

Effect 是一個javascript 對象,裡麵包含描述副作用的訊息,可以通過yield 傳達給sagaMiddleware 執行

在redux-saga世界裡,所有的Effect都必須被yield才會執行,所以有人寫了eslint-plugin-redux-saga來檢查是否每個Effect都被yield。並且原則上來說,所有的yield 後面也只能跟Effect,以保證代碼的易測性。

例如:

yieldfetch(UrlMap.fetchData);

應該用call Effect :

yieldcall(fetch,UrlMap.fetchData)

從而可以使代碼可測:

assert.deepEqual(iterator.next().value,call(fetch,UrlMap.fetchData))

關於各個Effect 的具體介紹,文檔已經寫得很詳細了,這裡只做簡要介紹。

1、put

作用和redux 中的dispatch 相同。

yieldput({type:'CLICK_BTN'});

2、select

作用和redux thunk 中的getState 相同。

constid=yieldselect(state=>state.id);

3、take

等待redux dispatch 匹配某個pattern 的action 。

在這個例子中,先等待一個按鈕點擊的action ,然後執行按鈕點擊的saga:

while(true){yieldtake('CLICK_BUTTON');yieldfork(clickButtonSaga);}

再舉一個利用take 實現logMiddleware 的例子:

while (true) { const action = yield take('*'); const newState = yield select(); console.log('received action:', action); console.log('state become:', newState); }

這種監聽一個action ,然後執行相應任務的方式,在redux-saga 中非常常用,因此redux-saga 提供了一個輔助Effect —— takeEvery ,讓watch/worker 的代碼更加清晰。

yieldtakeEvery('*',function*logger(action){constnewState=yieldselect();console.log('received action:',action);console.log('state become:',newState);});

4、阻塞調用和無阻塞調用

redux-saga 可以用fork 和call 來調用子saga ,其中fork 是無阻塞型調用,call 是阻塞型調用。

如果看過saga 的論文,就知道saga 是由許多子saga (或者subtransaction)組合起來的。 fork Effect 和它的字面意思一樣,即創建一個子saga 。

4.1、fork

下面寫一個倒數的例子,當接收到BEGIN_COUNT 的action,則開始倒數,而接收到STOP_COUNT 的action, 則停止倒數。

function*count(number){letcurrNum=number;while(currNum>=0){console.log(currNum--);yielddelay(1000);}}functioncountSaga*(){while(true){const{payload:number}=yieldtake(BEGIN_COUNT);constcountTaskId=yieldfork(count,number);yieldtake(STOP_TASK);yieldcancel(countTaskId);}}

4.2、call

有阻塞地調用saga 或者返回promise 的函數。

同樣寫一個例子:

constproject=yieldcall(fetch,{url:UrlMap.fetchProject});constmembers=yieldcall(fetchMembers,project.id);

另附redux-saga 文檔:

英文文檔

中文文檔

傳統異步中間件簡介

在介紹redux-saga 優缺點之前,這裡先簡要介紹傳統的redux 異步中間件,以便和redux-saga 做比較。對傳統異步中間件已經充分了解的讀者,可以直接跳到“redux-saga 優缺點分析” 進行閱讀。

1. fetch-middleware

使用redux的前端技術團隊或個人,大多數都有一套自己fetch-middleware,一來可以封裝異步請求的業務邏輯,避免重複代碼,二來可以寫一些公共的異步請求邏輯,比如異常接口數據採集、接口緩存、接口處理等等。例如redux-composable-fetchredux-api-middleware

在當前redux 社區中,fetch-middleware 封裝結果一般如下:

functionloadData(id){return{url:'/api.json',types:[LOADING_ACTION_TYPE,SUCCESS_ACTION_TYPE,FAILURE_ACTION_TYPE],params:{id,},};}

值得一提的是,大多數fetch-middleware 都會用到一個小技巧—— 把最終處理好的promise 返回出來,以便在thunk-middleware 中復用,並組織不同異步過程的先後邏輯。

function loadDetailThunk ( id ) { return ( dispatch ) => { // 先请求到loadData 的结果,再请求loadDetail dispatch ( loadData ( id )). then ( result => { const { id : detailId } = result ; dispatch ( loadDetail ( detailId )); }); }; }

這個技巧在redux-saga 中也同樣有效。

function*loadDetailSaga(id){constresult=yieldput.sync(loadData(id));const{id:detailId}=result;yieldput.sync(loadDetail(detailId));}

2. redux-thunk-middleware

redux 中大量應用了thunk 的概念,例如getState 以延遲執行的方式可以始終獲得最新值,redux-thunk 以延遲執行的方式把副作用的責任推卸到用戶身上。

任何異步問題都能在thunk 中解決。

3. sequence-middleware

sequence-middleware 用於保證action 依次執行,無論是異步action 還是普通aciton ,和fetch-middleware 配合使用非常方便。

這裡可以把每個action 可以寫成thunk action,在thunk 函數內從store 拿到參數,避免action 之間的依賴。這樣不管業務邏輯有多複雜,都可以通過用sequence action 輕易組織。

functionloadDetailThunk(){returnfunction(dispatch,getState){constdetailId=_.get(getState(),`${currPath}.detailId`);dispatch({url:UrlMap.getDetail,params:{detailId},});};}functionloadDetail(){return[loadData(),loadDetailThunk()];}

redux-saga 優缺點分析

缺點

  • redux-saga 不強迫我們捕獲異常,這往往會造成異常發生時難以發現原因。因此,一個良好的習慣是,相信任何一個過程都有可能發生異常。如果出現異常但沒有被捕獲,redux-saga 的錯誤棧會給你一種一臉懵逼的感覺。

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

  • 你團隊中使用的其它異步中間件,或許難以和redux-saga 搭配良好。或許需要花費一些代價,用redux-saga 來重構一部分中間件。

優點

  • 保持action 的簡單純粹,aciton 不再像原來那樣五花八門,讓人眼花繚亂。 task 的模式使代碼更加清晰。

  • redux-saga 提供了豐富的Effects,以及sagas 的機制(所有的saga 都可以被中斷),在處理複雜的異步問題上十分趁手。如果你的應用屬於寫操作密集型或者業務邏輯複雜,快讓redux-saga 來拯救你。

  • 擴展性強。

  • 聲明式的Effects,使代碼更易測試, 查看詳情

利用redux-saga 寫redux 中間件

用redux-saga 來寫中間件,可謂事半功倍。這裡舉一個輪詢中間件的例子。

function*pollingSaga(fetchAction){const{defaultInterval,mockInterval}=fetchAction;while(true){try{constresult=yieldput.sync(fetchAction);constinterval=mockInterval||result.interval;yielddelay(interval*1000);}catch(e){yielddelay(defaultInterval*1000);}}}function*beginPolling(pollingAction){const{pollingUrl,defaultInterval=300,mockInterval,types,params={}}=pollingAction;if(!types[1]){console.error('pollingAction pattern error',pollingAction);throwError('pollingAction types[1] is null');}constfetchAction={url:pollingUrl,types,params,mockInterval,defaultInterval,};constpollingTaskId=yieldfork(pollingSaga,fetchAction);constpattern=action=>action.type===types[1]&&action.stopPolling;yieldtake(pattern);yieldcancel(pollingTaskId);}function*pollingSagaMiddleware(){yieldtakeEvery(action=>{const{pollingUrl,types}=action;returnpollingUrl&&types&&types.length;},beginPolling);};

最後,redux-saga在實踐的沉澱,我已經總結到redux-saga-sugar ,歡迎點贊~

What do you think?

Written by marketer

blank

📡使用Github做一個完全免費的個人網站(步驟很細)

blank

從前端工程師到AR工程師