摘要:舉例來說一個異步的請求場景,可以如下實現任何異步的邏輯都可以,如等等也可以使用的和。實際上在中,一個就是一個函數。
3.4 redux 異步書籍完整目錄
在大多數的前端業務場景中,需要和后端產生異步交互,在本節中,將詳細講解 redux 中的異步方案以及一些異步第三方組件,內容有:
redux 異步流
redux-thunk
redux-promise
redux-saga
3.4.1 redux 異步流前面講的 redux 中的數據流都是同步的,流程如下:
view -> actionCreator -> action -> reducer -> newState -> container component
但同步數據不能滿足真實業務開發,真實業務中異步才是主角,那如何將異步處理結合到上邊的流程中呢?
其實 redux 并未有和異步相關的概念,我們可以用任何原來實現異步的方式應用到 redux 數據流中,最簡單的方式就是延遲 dispatch action,以 setTimeout 為例:
this.dispatch({ type: "SYNC_SOME_ACTION"}) window.setTimeout(() => { this.dispatch({ type: "ASYNC_SOME_ACTION" }) }, 1000)
這種方式最簡單直接,但是有如下問題:
如果有多個類似的 action 觸發場景,異步邏輯不能重用
異步處理代碼不能統一處理,最簡單的例子就是節流
解決上面兩個問題的辦法很簡單,把異步的代碼剝離出來:
someAction.js
function dispatchSomeAction(dispatch, payload) { // ..調用控制邏輯... dispatch({ type: "SYNC_SOME_ACTION"}) window.setTimeout(() => { dispatch({ type: "ASYNC_SOME_ACTION" }) }, 1000) }
然后組件只需要調用:
import {dispatchSomeAction} from "someAction.js" dispatchSomeAction(dispatch, payload);
基于這種方式上面的流程就改為了:
view -> asyncActionDispatcher -> wait -> action -> reducer -> newState -> container component
asyncActionDispatcher 和 actionCreator 是十分類似的, 所以簡單而言就可以把它理解為 asyncActionCreator , 所以新的流程為:
view -> asyncActionCreator -> wait -> action -> reducer -> newState -> container component
但是上面的方法有一些缺點
同步調用和異步調用的方式不相同:
同步的情況: store.dispatch(actionCreator(payload))
異步的情況: asyncActionCreator(store.dispatch, payload)
幸運的是在 redux 中通過 middleware 機制可以很容易的解決上面的問題
通過 middleware 實現異步我們已經很清楚一個 middleware 的結構 ,其核心的部分為
function(action) { // 調用后面的 middleware next(action) }
middleware 完全掌控了 reducer 的觸發時機, 也就是 action 到了這里完全由中間件控制,不樂意就不給其他中間件處理的機會,而且還可以控制調用其他中間件的時機。
舉例來說一個異步的 ajax 請求場景,可以如下實現:
function (action) { // async call fetch("....") .then( function resolver(ret) { newAction = createNewAction(ret, action) next(newAction) }, function rejector(err) { rejectAction = createRejectAction(err, action) next(rejectAction) }) }); }
任何異步的 javascript 邏輯都可以,如: ajax callback, Promise, setTimeout 等等, 也可以使用 es7 的 async 和 await。
第三方異步組件上面的實現方案只是針對具體的場景設計的,那如果是如何解決通用場景下的問題呢,其實目前已經有很多第三方 redux 組件支持異步 action,其中如:
redux-thunk
redux-promise
redux-saga
這些組件都有很好的擴展性,完全能滿足我們開發異步流程的場景,下面來一一介紹
3.4.3 redux-thunk redux-thunk 介紹redux-thunk 是 redux 官方文檔中用到的異步組件,實質就是一個 redux 中間件,thunk 聽起來是一個很陌生的詞語,先來認識一下什么叫 thunk
A thunk is a function that wraps an expression to delay its evaluation.
簡單來說一個 thunk 就是一個封裝表達式的函數,封裝的目的是延遲執行表達式
// 1 + 2 立即被計算 = 3 let x = 1 + 2; // 1 + 2 被封裝在了 foo 函數內 // foo 可以被延遲執行 // foo 就是一個 thunk let foo = () => 1 + 2;
redux-thunk 是一個通用的解決方案,其核心思想是讓 action 可以變為一個 thunk ,這樣的話:
同步情況:dispatch(action)
異步情況:dispatch(thunk)
我們已經知道了 thunk 本質上就是一個函數,函數的參數為 dispatch, 所以一個簡單的 thunk 異步代碼就是如下:
this.dispatch(function (dispatch){ setTimeout(() => { dispatch({type: "THUNK_ACTION"}) }, 1000) })
之前已經講過,這樣的設計會導致異步邏輯放在了組件中,解決辦法為抽象出一個 asyncActionCreator, 這里也一樣,我們就叫 thunkActionCreator 吧,上面的例子可以改為:
//actions/someThunkAction.js export function createThunkAction(payload) { return function(dispatch) { setTimeout(() => { dispatch({type: "THUNK_ACTION", payload: payload}) }, 1000) } } // someComponent.js this.dispatch(createThunkAction(payload))安裝和使用
第一步:安裝
$ npm install redux-thunk
第二步: 添加 thunk 中間件
import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import rootReducer from "./reducers/index"; const store = createStore( rootReducer, applyMiddleware(thunk) );
第三步:實現一個 thunkActionCreator
//actions/someThunkAction.js export function createThunkAction(payload) { return function(dispatch) { setTimeout(() => { dispatch({type: "THUNK_ACTION", payload: payload}) }, 1000) } }
第三步:組件中 dispatch thunk
this.dispatch(createThunkAction(payload));
thunk 源碼擁有 dispatch 方法的組件為 redux 中的 container component
說了這么多,redux-thunk 是不是做了很多工作,實現起來很復雜,那我們來看看 thunk 中間件的實現
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === "function") { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
就這么簡單,只有 14 行源碼,但是這簡短的實現卻能完成復雜的異步處理,怎么做到的,我們來分析一下:
判斷如果 action 是 function 那么執行 action(dispatch, getState, ...)
action 也就是一個 thunk
執行 action 相當于執行了異步邏輯
action 中執行 dispatch
開始新的 redux 數據流,重新回到最開始的邏輯(thunk 可以嵌套的原因)
把執行的結果作為返回值直接返回
直接返回并沒有調用其他中間件,也就意味著中間件的執行在這里停止了
可以對返回值做處理(后面會講如果返回值是 Promise 的情況)
如果不是函數直接調用其他中間件并返回
理解了這個過后是不是對 redux-thunk 的使用思路變得清晰了
thunk 的組合根據 redux-thunk 的特性,可以做出很有意思的事情
可以遞歸的 dispatch(thunk) => 實現 thunk 的組合;
thunk 運行結果會作為 dispatch返回值 => 利用返回值為 Promise 可以實現多個 thunk 的編排;
thunk 組合例子:
function thunkC() { return function(dispatch) { dispatch(thunkB()) } } function thunkB() { return function (dispatch) { dispatch(thunkA()) } } function thunkA() { return function (dispatch) { dispatch({type: "THUNK_ACTION"}) } }
Promise 例子
function ajaxCall() { return fetch(...); } function thunkC() { return function(dispatch) { dispatch(thunkB(...)) .then( data => dispatch(thunkA(data)), err => dispatch(thunkA(err)) ) } } function thunkB() { return function (dispatch) { return ajaxCall(...) } } function thunkA() { return function (dispatch) { dispatch({type: "THUNK_ACTION"}) } }3.4.4 redux-promise
另外一個 redux 文檔中提到的異步組件為 redux-promise, 我們直接分析一下其源碼吧
import { isFSA } from "flux-standard-action"; function isPromise(val) { return val && typeof val.then === "function"; } export default function promiseMiddleware({ dispatch }) { return next => action => { if (!isFSA(action)) { return isPromise(action) ? action.then(dispatch) : next(action); } return isPromise(action.payload) ? action.payload.then( result => dispatch({ ...action, payload: result }), error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); } ) : next(action); }; }
大概的邏輯就是:
如果不是標準的 flux action,那么判斷是否是 promise, 是執行 action.then(dispatch),否執行 next(action)
如果是標準的 flux action, 判斷 payload 是否是 promise,是的話 payload.then 獲取數據,然后把數據作為 payload 重新 dispatch({ ...action, payload: result}) , 否執行 next(action)
結合 redux-promise 可以利用 es7 的 async 和 await 語法,簡化異步的 promiseActionCreator 的設計, eg:
export default async (payload) => { const result = await somePromise; return { type: "PROMISE_ACTION", payload: result.someValue; } }
如果對 es7 async 語法不是很熟悉可以看下面兩個例子:
async 關鍵字可以總是返回一個 Promise 的 resolve 結果或者 reject 結果
async function foo() { if(true) return "Success!"; else throw "Failure!"; } // 等價于 function foo() { if(true) return Promise.resolve("Success!"); else return Promise.reject("Failure!"); }
在 async 關鍵字中可以使用 await 關鍵字,其目的是 await 一個 promise, 等待 promise resolve 和 reject
eg:
async function foo(aPromise) { const a = await new Promise(function(resolve, reject) { // This is only an example to create asynchronism window.setTimeout( function() { resolve({a: 12}); }, 1000); }) console.log(a.a) return a.a } // in console > foo() > Promise {_c: Array[0], _a: undefined, _s: 0, _d: false, _v: undefined…} > 12
可以看到在控制臺中,先返回了一個 promise,然后輸出了 12
async 關鍵字可以極大的簡化異步流程的設計,避免 callback 和 thennable 的調用,看起來和同步代碼一致。
3.4.5 redux-saga redux-saga 介紹redux-saga 也是解決 redux 異步 action 的一個中間件,不過和之前的設計有本質的不同
redux-saga 完全基于 Es6 的 Generator Function
不使用 actionCreator 策略,而是通過監控 action, 然后在自動做處理
所有帶副作用的操作(異步代碼,不確定的代碼)都被放到 saga 中
那到底什么是 sagaredux-saga 實際也沒有解釋什么叫 saga ,通過引用的參考:
The term saga is commonly used in discussions of CQRS to refer to a piece of code that coordinates and routes messages between bounded contexts and aggregates.
這個定義的核心就是 CQRS-查詢與責任分離 ,對應到 redux-sage 就是 action 與 處理函數的分離。 實際上在 redux-saga 中,一個 saga 就是一個 Generator 函數。
eg:
import { takeEvery, takeLatest } from "redux-saga" import { call, put } from "redux-saga/effects" import Api from "..." /* * 一個 saga 就是一個 Generator Function * * 每當 store.dispatch `USER_FETCH_REQUESTED` action 的時候都會調用 fetchUser. */ function* mySaga() { yield* takeEvery("USER_FETCH_REQUESTED", fetchUser); } /** * worker saga: 真正處理 action 的 saga * * USER_FETCH_REQUESTED action 觸發時被調用 * @param {[type]} action [description] * @yield {[type]} [description] */ function* fetchUser(action) { try { const user = yield call(Api.fetchUser, action.payload.userId); yield put({type: "USER_FETCH_SUCCEEDED", user: user}); } catch (e) { yield put({type: "USER_FETCH_FAILED", message: e.message}); } }一些基本概念
watcher saga
負責編排和派發任務的 saga
worker saga
真正負責處理 action 的函數
saga helper
如上面例子中的 takeEvery,簡單理解就是用于監控 action 并派發 action 到 worker saga 的輔助函數
Effect
redux-saga 完全基于 Generator 構建,saga 邏輯的表達是通過 yield javascript 對象來實現,這些對象就是Effects。
這些對象相當于描述任務的規范化數據(任務如執行異步函數,dispatch action 到一個 store),這些數據被發送到 redux-saga 中間件中執行,如:
put({type: "USER_FETCH_SUCCEEDED", user: user}) 表示要執行 dispatch({{type: "USER_FETCH_SUCCEEDED", user: user}}) 任務
call(fetch, url) 表示要執行 fetch(url)
通過這種 effect 的抽象,可以避免 call 和 dispatch 的立即執行,而是描述要執行什么任務,這樣的話就很容易對 saga 進行測試,saga 所做的事情就是將這些 effect 編排起來用于描述任務,真正的執行都會放在 middleware 中執行。
安裝和使用第一步:安裝
$ npm install --save redux-saga
第二步:添加 saga 中間件
import { createStore, applyMiddleware } from "redux" import createSagaMiddleware from "redux-saga" import reducer from "./reducers" import mySaga from "./sagas" // 創建 saga 中間件 const sagaMiddleware = createSagaMiddleware() // 添加到中間件中 const store = createStore( reducer, applyMiddleware(sagaMiddleware) ) // 立即運行 saga ,讓監控器開始監控 sagaMiddleware.run(mySaga)
第三步:定義 sagas/index.js
import { takeEvery } from "redux-saga" import { put } from "redux-saga/effects" export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) // 將異步執行 increment 任務 export function* incrementAsync() { yield delay(1000) yield put({ type: "INCREMENT" }) } // 在每個 INCREMENT_ASYNC action 調用后,派生一個新的 incrementAsync 任務 export default function* watchIncrementAsync() { yield* takeEvery("INCREMENT_ASYNC", incrementAsync) }
第四步:組件中調用
this.dispatch({type: "INCREMENT_ASYNC"})
redux-saga 基于 Generator 有很多高級的特性, 如:
基于 take Effect 實現更自由的任務編排
fork 和 cancel 實現非阻塞任務
并行任何和 race 任務
saga 組合 ,yield* saga
因篇幅有限,這部分內容在下一篇講解
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/79756.html
摘要:數組為新的數組,包含了方法將新的和結合起來,生成一個新的方法返回的新增了一個方法,這個新的方法是改裝過的,也就是封裝了中間件的執行。 書籍完整目錄 3.3 理解 Redux 中間件 showImg(https://segmentfault.com/img/bVymkt); 這一小節會講解 redux 中間件的原理,為下一節講解 redux 異步 action 做鋪墊,主要內容為: ...
摘要:通過可以實現很多有趣的簡潔的控制。這里默認使用到了的一個特性,如果某一個任務成功了過后,其他任務都會被。組合是的內關鍵字,使用的場景是一個。 書籍完整目錄 3.5 compose redux sages showImg(https://segmentfault.com/img/bVyoVa); 基于 redux-thunk 的實現特性,可以做到基于 promise 和遞歸的組合編排,而...
摘要:單向數據流應用的核心設計模式,數據流向自頂向下我也是性子急的人,按照技術界的慣例,在學習一個技術前,首先得說一句。然而的單向數據流的設計讓前端定位變得簡單,頁面的和數據的對應是唯一的我們可以通過定位數據變化就可以定位頁面展現問題。 書籍完整目錄 1.1 React 介紹 showImg(https://segmentfault.com/img/bVvJgS); 1.1.1 React ...
摘要:另外一點是組件應該盡量保證獨立性,避免和外部的耦合,使用全局事件造成了和外部事件的耦合。明確的職責分配也增加了應用的確定性明確只有組件能夠知道狀態數據,且是對應部分的數據。 書籍完整目錄 4.2 react patterns 修改 Props Immutable data representation 確定性 在 getInitialState 中使用 props 私有狀態和...
閱讀 1378·2021-10-08 10:04
閱讀 2681·2021-09-22 15:23
閱讀 2724·2021-09-04 16:40
閱讀 1172·2019-08-29 17:29
閱讀 1492·2019-08-29 17:28
閱讀 2988·2019-08-29 14:02
閱讀 2221·2019-08-29 13:18
閱讀 838·2019-08-23 18:35