日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

精益 React 学习指南 (Lean React)- 3.4 掌控 redux 异步

發布時間:2025/6/15 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 精益 React 学习指南 (Lean React)- 3.4 掌控 redux 异步 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

書籍完整目錄

3.4 redux 異步

在大多數的前端業務場景中,需要和后端產生異步交互,在本節中,將詳細講解 redux 中的異步方案以及一些異步第三方組件,內容有:

  • redux 異步流

  • redux-thunk

  • redux-promise

  • redux-saga

3.4.1 redux 異步流

前面講的 redux 中的數據流都是同步的,流程如下:

view -> actionCreator -> action -> reducer -> newState -> container component
但同步數據不能滿足真實業務開發,真實業務中異步才是主角,那如何將異步處理結合到上邊的流程中呢?

3.4.2 實現異步的方式

其實 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) {// 調用后面的 middlewarenext(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));

    擁有 dispatch 方法的組件為 redux 中的 container component

    thunk 源碼

    說了這么多,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!';elsethrow 'Failure!'; }// 等價于function foo() {if(true)return Promise.resolve('Success!');elsereturn 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 asynchronismwindow.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 中

  • 那到底什么是 saga

    redux-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

  • 因篇幅有限,這部分內容在下一篇講解

    總結

    以上是生活随笔為你收集整理的精益 React 学习指南 (Lean React)- 3.4 掌控 redux 异步的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。