Redux 的核心理念是嚴(yán)格的單向數(shù)據(jù)流馅袁,只能通過 dispatch(action) 的方式修改 store昔馋,流程如下:
view ->? action -> reducer -> store
但是在業(yè)務(wù)復(fù)雜的以及和api數(shù)據(jù)對接的過程中肯定會遇到大量的異步操作蜓谋。我們?nèi)绾蝸斫鉀Q這些場景呢富俄?
redux中間件
什么是redux中間件
redux中間件
這里我們先從redux的中間件說起呕臂, 中間件拾稳,顧名思義:進行中間處理的物件。類似于面向?qū)ο缶幊痰腁OP編程思想(不了解AOP的可以忽略這句話)
簡單的說:中間件可以控制在store dispatch action之前和之后的業(yè)務(wù)邏輯曲秉。也就是說 中間件實現(xiàn)了改寫 store.dispatch 方法實現(xiàn)了action -> reducer的攔截的行為采蚀。
如果我們分別注冊三個中間件: 中間件A 中間件B 中間件C
那么
中間件A -> 中間件B-> 中間件C-> 原始 dispatch -> 中間件C -> 中間件B -> 中間件A
和異步處理的關(guān)系
綜上所述:中間件可以領(lǐng)過的改變 dispatch的時機,這樣我們就可以很方便的處理異步場景了承二。
因此各種 redux異步處理中間件應(yīng)運而生榆鼠。比較知名的有redux-thunk和redux-saga。
redux-thunk
redux-thunk中間件可以讓action創(chuàng)建函數(shù)先不返回一個action對象亥鸠,而是返回一個函數(shù)妆够,函數(shù)傳遞兩個參數(shù)(dispatch,getState),在函數(shù)體內(nèi)進行業(yè)務(wù)邏輯的封裝
functionadd(){return{type:'ADD',? ? }}functionaddIfOdd(){return(dispatch, getState) =>{constcurrentValue = getState();if(currentValue %2==0) {returnfalse;? ? ? ? }//分發(fā)一個任務(wù)dispatch(add())? ? }}
詳細(xì)代碼可以查看分支:https://github.com/YahuiWong/react-native-typescript/tree/redux-thunk
redux-saga
sages 采用 Generator 函數(shù)來 yield Effects(包含指令的文本對象)。Generator 函數(shù)的作用是可以暫停執(zhí)行负蚊,再次執(zhí)行的時候從上次暫停的地方繼續(xù)執(zhí)行神妹。Effect 是一個簡單的對象,該對象包含了一些給 middleware 解釋執(zhí)行的信息家妆。你可以通過使用 effects API 如 fork鸵荠,call,take伤极,put蛹找,cancel 等來創(chuàng)建 Effect姨伤。( redux-saga API 參考)
如 yield call(fetch, '/products') 即 yield 了下面的對象,call 創(chuàng)建了一條描述結(jié)果的信息庸疾,然后乍楚,redux-saga middleware 將確保執(zhí)行這些指令并將指令的結(jié)果返回給 Generator:
// Effect -> 調(diào)用 fetch 函數(shù)并傳遞 `./products` 作為參數(shù){type: CALL,function: fetch,? args: ['./products']}
與 redux-thunk 不同的是,在 redux-saga 中届慈,UI 組件自身從來不會觸發(fā)任務(wù)徒溪,它們總是會 dispatch 一個 action 來通知在 UI 中哪些地方發(fā)生了改變,而不需要對 action 進行修改金顿。redux-saga 將異步任務(wù)進行了集中處理臊泌,且方便測試。
dispacth({ type: 'FETCH_REQUEST', url: /* ... */} );
所有的東西都必須被封裝在 sagas 中串绩。sagas 包含3個部分缺虐,用于聯(lián)合執(zhí)行任務(wù):
worker saga
做所有的工作,如調(diào)用 API礁凡,進行異步請求高氮,并且獲得返回結(jié)果
watcher saga
監(jiān)聽被 dispatch 的 actions,當(dāng)接收到 action 或者知道其被觸發(fā)時顷牌,調(diào)用 worker saga 執(zhí)行任務(wù)
root saga
立即啟動 sagas 的唯一入口
? 如何使用剪芍?
首先,我們得在文件入口中加入 saga 中間件窟蓝,并且啟動它罪裹,它會一直運行:
//...import{ createStore, applyMiddleware}from'redux';importcreateSagaMiddlewarefrom'redux-saga';importappReducerfrom'./reducers';importrootSagafrom'./saga';//...constsagaMiddleware = createSagaMiddleware()conststore=createStore(rootReducer,applyMiddleware(sagaMiddleware));sagaMiddleware.run(rootSaga)render(,document.getElementById('app'));
然后,就可以在 sagas 文件夾中集中寫 saga 文件了:
import{delay}from'redux-saga';import{put,takeEvery,all}from'redux-saga/effects';import{ADD}from'./actionsTypes';function*addSync(){yielddelay(1000);yieldput({type:ADD})}function*watchaddSync(){yieldtakeEvery("addSync",addSync)}exportdefaultfunction*rootSaga(){yieldall([? ? ? ? watchaddSync()? ? ])}
在 redux-saga 中的基本概念就是:sagas 自身不真正執(zhí)行副作用(如函數(shù) call)运挫,但是會構(gòu)造一個需要執(zhí)行作用的描述状共。中間件會執(zhí)行該副作用并把結(jié)果返回給 generator 函數(shù)。
對上述例子的說明:
(1)引入的 redux-saga/effects 都是純函數(shù)谁帕,每個函數(shù)構(gòu)造一個特殊的對象峡继,其中包含著中間件需要執(zhí)行的指令,如:call(fetchUrl, url) 返回一個類似于 {type: CALL, function: fetchUrl, args: [url]} 的對象匈挖。
(2)在 watcher saga watchFetchRequests中:
首先 yield take('FETCH_REQUEST') 來告訴中間件我們正在等待一個類型為 FETCH_REQUEST 的 action碾牌,然后中間件會暫停執(zhí)行 wacthFetchRequests generator 函數(shù),直到 FETCH_REQUEST action 被 dispatch儡循。一旦我們獲得了匹配的 action舶吗,中間件就會恢復(fù)執(zhí)行 generator 函數(shù)。
下一條指令 fork(fetchUrl, action.url) 告訴中間件去無阻塞調(diào)用一個新的 fetchUrl 任務(wù)择膝,action.url 作為 fetchUrl 函數(shù)的參數(shù)傳遞誓琼。中間件會觸發(fā) fetchUrl generator 并且不會阻塞 watchFetchRequests。當(dāng)fetchUrl 開始執(zhí)行的時候,watchFetchRequests 會繼續(xù)監(jiān)聽其它的 watchFetchRequests actions踊赠。當(dāng)然呵扛,JavaScript 是單線程的,redux-saga 讓事情看起來是同時進行的筐带。
(3)在 worker saga fetchUrl 中,call(fetch,url) 指示中間件去調(diào)用 fetch 函數(shù)缤灵,同時伦籍,會阻塞fetchUrl 的執(zhí)行,中間件會停止 generator 函數(shù)腮出,直到 fetch 返回的 Promise 被 resolved(或 rejected)帖鸦,然后才恢復(fù)執(zhí)行 generator 函數(shù)。
最后胚嘲,總結(jié)一下 redux-saga 的優(yōu)點:
(1)聲明式 Effects:所有的操作以JavaScript對象的方式被 yield作儿,并被 middleware 執(zhí)行。使得在 saga 內(nèi)部測試變得更加容易馋劈,可以通過簡單地遍歷 Generator 并在 yield 后的成功值上面做一個 deepEqual 測試攻锰。
(2)高級的異步控制流以及并發(fā)管理:可以使用簡單的同步方式描述異步流,并通過 fork 實現(xiàn)并發(fā)任務(wù)妓雾。
(3)架構(gòu)上的優(yōu)勢:將所有的異步流程控制都移入到了 sagas娶吞,UI 組件不用執(zhí)行業(yè)務(wù)邏輯,只需 dispatch action 就行械姻,增強組件復(fù)用性妒蛇。
詳細(xì)代碼可以查看分支:https://github.com/YahuiWong/react-native-typescript/tree/redux-saga?如果覺得有用,請Star?楷拳,謝謝绣夺!
參考: