<b>本文講的是[譯] Redux 異步四兄弟,</b>
筆者第一次接觸 Redux 的時候,就被這些異步的、帶有副作用的函數庫搞得“頭昏腦脹”。 雖然相關文檔還算齊全,但還是希望能夠結合實際項目去深入了解這些函數庫是如何解決 Redux 中的異步問題。進而快速上手,避免浪費過多時間。
在本教程中,筆者将應用上述函數庫,一步步地實作一個拉取資料并将資料存儲在 reducer 中的簡單例子。
如圖所示,上述函數庫最通用的模式之一就是發起一個 API 請求,顯示加載圖示,資料傳回後展示結果(如果出現錯誤則展示錯誤資訊)。筆者将依次使用上述 4 個函數庫實作該功能。
在本例中筆者将使用 React Native,當然使用 React 也是完全一樣的 —— 隻需要把 <code>View</code> 替換為 <code>div</code>, 把 <code>Text</code> 替換為 <code>p</code> 即可。 在本節中,筆者将僅僅實作一個簡單的 Redux 示例應用,以展示上述 4 個函數庫的用法。
首先運作 react-native init 指令建立一個空項目:
當然也可以使用 create-react-app:
然後進入項目目錄:
安裝所需依賴:
建立将要用到的相關目錄和檔案:
至此,所有依賴都已安裝完畢,相關檔案業已建立妥當,可以着手編碼開發了。
首先将 <code>index.ios</code> (ios) 或 <code>index.android.js</code> (android) 中的代碼更新如下:
從 <code>react-redux</code> 中引入 <code>Provider</code>。
引入 <code>configureStore</code>,随後将建立該檔案。
引入 <code>App</code> 作為本例應用中的入口元件。
調用 <code>configureStore()</code> 方法建立 store。
将 <code>App</code> 包裹在 <code>Provider</code> 中并傳入上述 store。
接着建立 actions 和 reducer 所涉及的相關常量,<code>constants.js</code> 檔案内容如下:
再接着建立 <code>dataReducer</code>,<code>dataReducer.js</code> 檔案内容如下:
引入相關常量。
該 reducer 的初始狀态 <code>initialState</code> 是一個對象,該對象由 1 個數組 <code>data</code> 和 3 個布爾類型的變量:<code>dataFetched</code> 、<code>isFetching</code> 以及 <code>error</code> 構成。
該 reducer 負責處理 3 種類型的 actions 并相應地更新狀态。例如,如果 action 的類型是<code>FETCHING_DATA_SUCCESS</code>, 則将新資料添加到狀态對象中并将 <code>isFetching</code> 設為 <code>false</code>。
接下來需要建立 reducer 的入口檔案,在該檔案中會對所有的 reducers 調用 <code>combineReducers</code>方法(在本例中隻有一個 reducer,即 <code>dataReducer.js</code> )。
<code>reducers/index.js</code> 檔案内容如下:
之後則需要建立相應的 actions,<code>actions.js</code> 檔案内容如下:
定義 4 個函數,其中 3 個 (<code>getData</code>、<code>getDataSuccess</code> 和 <code>getDataFailure</code>)會直接傳回 action,第 4 個(<code>fetchData</code>)則會更新一個 thunk (具體實作見下文)。
接着定義 configureStore:
從 <code>./reducers</code> 中引入 root reducer。
暴露用以建立 store 的函數接口。
最後, 對接頁面 UI 并綁定相應 props:
此處代碼不言自明 —— connect 方法用于将目前 Redux store 的狀态和引入的 actions 作為 props 傳入目标展示性元件中,即此例中的 <code>App</code>。
最後需要一個模拟的資料接口,該接口傳回一個 promise,該 promise 會在 3 秒鐘後 reslove,并傳回相應資料。對應檔案 <code>api.js</code> 内容如下:
在該檔案中,首先建立一個含有人員資訊的數組,然後暴露一個實作了上述模拟接口功能的方法。
首先需要建立一個 thunk
在 <code>actions.js</code> 檔案中,更新函數 <code>fetchData</code> 并引入 api:
此處 <code>fetchData</code> 函數是一個 thunk。當被調用時,fetchData 會傳回一個函數;該函數首先會 dispatch <code>getData</code> action,然後調用 <code>getPeople</code>,在 <code>getPeople</code> 傳回的 promise reslove 之後,會 dispatch <code>getDataSuccess</code> action。
接下來,需要更新 <code>configureStore</code> 函數以引入 thunk 中間件:
從 <code>redux</code> 引入 applyMiddleware。
從 <code>redux-thunk</code> 引入 <code>thunk</code>。
将 <code>applyMiddleware</code> 作為第二個參數傳遞給函數 <code>createStore</code>。
最後,更新 <code>app.js</code> 檔案來使用上述 thunk:
此處代碼主要有以下幾個要點:
為 TouchableHighlight 元件綁定 onPress 函數,當按壓事件觸發後調用<code>props.fetchData()</code>。
檢查 <code>props.appData.isFetching</code> 的值是否為 true, 如果是則傳回正在加載的文字提示。
檢查 <code>props.appData.data.length</code>,如果該值存在且不為 0,則周遊該數組,展示人員姓名和年齡資訊。
至此,當按下按鈕 Load Data 後,首先會看到正在加載的提示文字,3 秒後會看到人員資訊。
為了實作 Saga,首先需要更新 actions —— 删除 <code>actions.js</code> 檔案中除了如下代碼外的其它所有代碼:
該 action 會觸發我們即将建立的 saga。建立 <code>saga.js</code> 檔案,寫入如下代碼:
引入所需常量。
從 <code>redux-saga/effects</code> 中引入 <code>put</code> 和 <code>takeEvery</code>。當調用 <code>put</code> 函數時,Reduc Sage 會訓示中間件 dipatch 一個 action。<code>takeEvery</code> 函數則會監聽被 dispatch 了的 action(本例中即為 <code>FETCHING_DATA</code>),然後調用回調函數(本例中即為 <code>fetchData</code>)。
當 <code>fetchData</code> 被調用後,代碼會等待函數 <code>getPeople</code> 的傳回,如果傳回成功則 dispatch<code>FETCHING_DATA_SUCCCESS</code> action。
最後更新 <code>configureStore.js</code> 檔案,用 saga 替換 thunk。
在該檔案中既引入了上述 saga,又從 <code>redux-saga</code> 中引入了 <code>createSagaMiddleware</code>。在建立 store 時,傳入 <code>sagaMiddleware</code>,然後在傳回 store 之前調用 <code>sagaMiddleWare.run</code>。
至此,可以再次運作該程式并看到和使用 Redux Thunk 是同樣的效果!
注意:從 thunk 遷移到 saga 隻改變了 3 個檔案: <code>saga.js</code>、<code>configureStore.js</code> 以及<code>actions.js</code>。
首先還是需要更新 actions.js 檔案:
如上所示,将之前的 actions 更新為最早的 3 個 actions。
接着建立所謂的 epic —— 輸入 action stream 并輸出 action stream 的函數。
建立 <code>epic.js</code> 檔案并加入如下代碼:
一般在 RxJS 中,變量名中的 $ 符号用以表示該變量是某 stream 的引用。
引入常量 FETCHING_DATA。
引入 <code>getDataSuccess</code> 和 <code>getDataFailure</code> 函數。
從 rxjs 中引入 <code>rxjs</code> 和 <code>Observable</code>。
定義函數 <code>fetchUserEpic</code>。
最後,更新 configureStore,應用新中間件 —— epic。
<code>configureStore.js</code> 檔案内容如下:
至此,可以再次運作該程式并看到後之前一樣的效果!
正如你将要看到的一樣,相比于上述幾個函數庫而言,Redux Promise Middleware 極大地減少了代碼量。
相較于上述幾個函數庫,Redux Promise Middleware 有所不同 —— 它會接管你的 action 并基于 promise 狀态的不同在 action 類型名稱後添加 <code>_PENDING</code>、<code>_FULFILLED</code> 或 <code>_REJECTED</code>。
例如,如果調用如下函數:
那麼就會自動地 dispatch <code>FETCH_DATA_PENDING</code> action。
一旦 <code>getPeople</code> promise resolved,基于傳回結果的不同,會 dispatch <code>FETCH_DATA_FULFILLED</code>或 <code>FETCH_DATA_REJECTED</code> action。
讓我們通過現有的例子來了解該特性:
首先需要更新 <code>constants.js</code>,以使其比對我們将要用到的常量:
接着将 <code>actions.js</code> 檔案更新為隻有一個 <code>FETCH_DATA</code> 這一個 action。
接着基于上面新定義的常量更新 <code>dataReducer.js</code> 檔案:
最後更新 <code>configureStore</code>,應用 Redux Promise Middleware:
總的來說,筆者認為 Saga 更适用于較為複雜的應用,除此之外的其他所有情況 Redux Promise Middleware 都是十分合适的。筆者十分喜歡 Saga 中的 Generators 和 async-await,這些特性很有趣; 同時筆者也喜歡 Redux Promise Middleware,因為它極大地減少了代碼量。
如果對 RxJS 更為熟悉的話,筆者也許會偏向 Redux Observable;但還是有很多筆者了解不透徹的地方,是以無法自信地将其應用于生産環境中。
如果你喜歡這篇文章,歡迎推薦和分享!謝謝!
<b></b>
<b>原文釋出時間為:2017年3月14日</b>
<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>