首頁 > web前端 > js教程 > React Redux應用中的異步操作

React Redux應用中的異步操作

Lisa Kudrow
發布: 2025-02-16 12:02:12
原創
495 人瀏覽過

Async Operations in React Redux Applications

關鍵要點

  • JavaScript 的單線程特性意味著異步操作(如 API 調用)對於非阻塞式 UI 至關重要,高效處理這些操作在 React Redux 應用中至關重要。
  • Redux Thunk、Redux-Saga 和 Redux-Observables 是流行的 Redux 異步操作管理中間件,它們各自根據應用的複雜性和需求提供不同的優勢。
  • Redux Thunk 通過允許返回函數而不是 action 來簡化異步 action 分發,從而能夠在這些函數中進行順序 API 調用和處理相關數據。
  • Redux-Saga 利用 ES6 生成器為複雜場景(如處理競爭條件、暫停和取消操作)提供更強大的解決方案,使其適合大型應用。
  • Redux-Observables 利用 RxJS 提供了一種強大的方法來通過 action 流管理副作用,在復雜的應用程序架構中提供出色的可擴展性和對異步操作的控制。

此文章最初發表於 Codebrahma。

JavaScript 是一種單線程編程語言。也就是說,當你編寫如下代碼時……

Async Operations in React Redux Applications

……第二行只有在第一行執行完畢後才會執行。大多數情況下這不會有問題,因為客戶端或服務器每秒執行數百萬次計算。只有當我們執行代價高昂的計算(需要相當長的時間才能完成的任務——網絡請求需要一些時間才能返回)時,我們才會注意到這些影響。

為什麼我只在這裡展示 API 調用(網絡請求)?其他異步操作呢? API 調用是一個非常簡單且有用的例子,用於描述如何處理異步操作。還有其他操作,例如 setTimeout()、性能密集型計算、圖像加載和任何事件驅動的操作。

在構建應用程序時,我們需要考慮異步執行如何影響結構。例如,將 fetch() 視為從瀏覽器執行 API 調用(網絡請求)的函數。 (忽略它是否是 AJAX 請求。只需將其行為視為異步或同步。)請求在服務器上處理時經過的時間不會發生在主線程上。因此,您的 JS 代碼將繼續執行,一旦請求返迴響應,它將更新線程。

考慮這段代碼:

userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId
userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
登入後複製
登入後複製
登入後複製
登入後複製

在這種情況下,由於 fetch() 是異步的,當我們嘗試獲取 userDetails 時,我們將不會擁有 userId。因此,我們需要以一種確保第二行僅在第一行返迴響應後才執行的方式來構建它。

大多數現代網絡請求實現都是異步的。但這並不總是有效,因為我們依賴於先前 API 響應數據來進行後續 API 調用。讓我們看看如何在 ReactJS/Redux 應用程序中特別構建它。

React 是一個用於創建用戶界面的前端庫。 Redux 是一個狀態容器,可以管理應用程序的整個狀態。將 React 與 Redux 結合使用,我們可以創建高效且可擴展的應用程序。在這樣的 React 應用程序中,有多種方法可以構建異步操作。對於每種方法,我們將討論其在以下因素方面的優缺點:

  • 代碼清晰度
  • 可擴展性
  • 易於錯誤處理

對於每種方法,我們將執行這兩個 API 調用:

1. 從 userDetails(第一個 API 響應)中獲取 city

假設端點是 /details。它將在響應中包含城市。響應將是一個對象:

userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId
userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
登入後複製
登入後複製
登入後複製
登入後複製

2. 基於用戶 city,我們將獲取該 city 中的所有餐廳

假設端點是 /restuarants/:city。響應將是一個數組:

userDetails: {
  …
  city: 'city',
  …
};
登入後複製
登入後複製

請記住,我們只有在完成第一個請求後才能發出第二個請求(因為它依賴於第一個請求)。讓我們看看各種方法:

  • 直接使用 Promise 或 async/await 與 setState
  • 使用 Redux Thunk
  • 使用 Redux-Saga
  • 使用 Redux Observables

我特別選擇了上述方法,因為它們是大型項目中最常用的方法。仍然存在其他方法可能更特定於特定任務,並且不具備複雜應用程序所需的所有功能(例如 redux-async、redux-promise、redux-async-queue)。

Promises

Promise 是一個對象,它可能在將來的某個時間產生單個值:已解析的值或未解析的原因(例如,發生網絡錯誤)。 — Eric Elliot

在我們的例子中,我們將使用 axios 庫來獲取數據,當我們發出網絡請求時,它會返回一個 Promise。該 Promise 可能會解析並返迴響應或拋出錯誤。因此,一旦 React 組件 掛載,我們可以直接像這樣獲取:

['restaurant1', 'restaurant2', …]
登入後複製

通過這種方式,當狀態發生變化(由於獲取)時,組件 將自動重新渲染並加載餐廳列表。

Async/await 是一種新的實現,我們可以用它來進行異步操作。例如,可以通過以下方式實現相同的功能:

componentDidMount() {
  axios.get('/details') // 获取用户详细信息
    .then(response => {
      const userCity = response.city;
      axios.get(`/restaurants/${userCity}`)
        .then(restaurantResponse => {
          this.setState({
            listOfRestaurants: restaurantResponse, // 设置状态
          })
        })
    })
}
登入後複製

這兩種方法都是最簡單的。由於整個邏輯都在組件內部,因此我們可以很容易地在組件加載後一次性獲取所有數據。

此方法的缺點

當進行基於數據的複雜交互時,問題就會出現。例如,考慮以下情況:

Async Operations in React Redux Applications

  • 我們不希望執行 JS 的線程被網絡請求阻塞。
  • 所有上述情況都會使代碼變得非常複雜,難以維護和測試。
  • 此外,可擴展性將是一個大問題,因為如果我們計劃更改應用程序的流程,我們需要從組件中刪除所有獲取操作。
  • 想像一下,如果組件位於父-子樹的頂部,我們會怎麼做。然後我們需要更改所有依賴於數據的表示組件。
  • 還要注意,整個業務邏輯都在組件內部。

我們如何改進?

  1. 狀態管理 在這些情況下,使用全局存儲實際上可以解決我們一半的問題。我們將使用 Redux 作為我們的全局存儲。

  2. 將業務邏輯移動到正確的位置 如果我們考慮將業務邏輯移出組件,那麼我們究竟可以在哪裡這樣做?在 actions 中?在 reducers 中?通過中間件? Redux 的架構是同步的。一旦你分發一個 action(JS 對象)並且它到達存儲,reducer 就會對其進行操作。

  3. 確保有一個單獨的線程執行異步代碼,並且可以通過訂閱檢索對全局狀態的任何更改

Async Operations in React Redux Applications

由此,我們可以了解到,如果我們將所有獲取邏輯移動到 reducer 之前——即 action 或中間件——那麼就可以在正確的時間分發正確的 action。例如,一旦獲取開始,我們可以分發 dispatch({ type: 'FETCH_STARTED' }),當它完成時,我們可以分發 dispatch({ type: 'FETCH_SUCCESS' })

想要開發一個 React JS 應用程序?

使用 Redux Thunk

Redux Thunk 是 Redux 的中間件。它基本上允許我們返回函數而不是對像作為 action。這通過提供 dispatchgetState 作為函數的參數來提供幫助。我們有效地使用 dispatch 在正確的時間分發必要的 actions。好處是:

  • 允許在函數內進行多次分發
  • 業務邏輯與獲取的關聯將從 React 組件移到 actions 中。

在我們的例子中,我們可以像這樣重寫 action:

userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId
userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
登入後複製
登入後複製
登入後複製
登入後複製

如你所見,我們現在可以很好地控制何時分發哪種類型的 action。每個函數調用(如 fetchStarted()fetchUserDetailsSuccess()fetchRestaurantsSuccess()fetchError())都會分發一個類型為普通 JavaScript 對象的 action,如果需要,還可以添加其他詳細信息。因此,現在 reducer 的任務是處理每個 action 並更新視圖。我沒有討論 reducer,因為它從這裡開始很簡單,並且實現可能會有所不同。

為了使這能夠工作,我們需要將 React 組件與 Redux 連接起來,並使用 Redux 庫將 action 與組件綁定。一旦完成,我們可以簡單地調用 this.props.getRestaurants(),這反過來將處理所有上述任務並根據 reducer 更新視圖。

就其可擴展性而言,Redux Thunk 可用於不涉及對異步 action 進行複雜控制的應用程序。此外,它與其他庫無縫協同工作,如下一節主題中所述。

但是,使用 Redux Thunk 來執行某些任務仍然有點困難。例如,我們需要暫停中間的獲取操作,或者當有多個這樣的調用時,只允許最新的調用,或者如果其他 API 獲取此數據並且我們需要取消。

我們仍然可以實現這些,但是精確地執行會比較複雜。與其他庫相比,複雜任務的代碼清晰度會略差,並且維護起來會比較困難。

使用 Redux-Saga

使用 Redux-Saga 中間件,我們可以獲得額外的優勢來解決上述大多數功能。 Redux-Saga 是基於 ES6 生成器開發的。

Redux-Saga 提供了一個 API,有助於實現以下目標:

  • 阻塞事件,在同一行阻塞線程,直到完成某些操作
  • 非阻塞事件,使代碼異步
  • 處理多個異步請求之間的競爭
  • 暫停/節流/去抖動任何 action

Saga 如何工作?

Saga 使用 ES6 生成器和 async/await API 的組合來簡化異步操作。它基本上在其單獨的線程上執行其工作,我們可以在其中進行多個 API 調用。我們可以使用它們的 API 使每個調用同步或異步,具體取決於用例。 API 提供了使線程在同一行等待直到請求返迴響應的功能。除此之外,此庫還提供了許多其他 API,這使得 API 請求非常易於處理。

考慮我們之前的示例:如果我們初始化一個 saga 並根據其文檔中提到的內容將其與 Redux 配置,我們可以執行以下操作:

userDetails: {
  …
  city: 'city',
  …
};
登入後複製
登入後複製

因此,如果我們使用類型為 FETCH_RESTAURANTS 的簡單 action 進行分發,Saga 中間件將偵聽並響應。實際上,沒有一個 action 被中間件使用。它只是偵聽並執行一些其他任務,如果需要,則分發新的 action。通過使用此架構,我們可以分發多個請求,每個請求描述:

  • 第一個請求何時開始
  • 第一個請求何時完成
  • 第二個請求何時開始

等等。

此外,您可以看到 fetchRestaurantSaga() 的優點。我們目前使用 call API 來實現阻塞調用。 Saga 提供其他 API,例如 fork(),它實現非阻塞調用。我們可以組合阻塞和非阻塞調用來維護適合我們應用程序的結構。

就可擴展性而言,使用 saga 有益:

  • 我們可以根據任何特定任務來構建和分組 saga。我們可以通過簡單地分發 action 來從另一個 saga 觸發一個 saga。
  • 由於它是中間件,我們編寫的 action 將是普通的 JS 對象,與 thunk 不同。
  • 由於我們將業務邏輯放在 saga 內部(這是一個中間件),如果我們知道 saga 的功能是什麼,那麼理解它的 React 部分就會容易得多。
  • 可以通過 try/catch 模式輕鬆監控錯誤並將其分發到存儲。

使用 Redux-Observables

正如其文檔中“Epic 是 redux-observable 的核心原語”部分所述:

  1. Epic 是一個函數,它接收 action 流並返回 action 流。也就是說,Epic 與正常的 Redux 分發通道並行運行,在 reducer 已經接收它們之後。
  2. action 始終在 epic 接收它們之前通過 reducer 運行。 Epic 只接收並輸出另一個 action 流。這與 Redux-Saga 類似,因為沒有一個 action 被中間件使用。它只是偵聽並執行一些其他任務。

對於我們的任務,我們可以簡單地編寫如下代碼:

userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId
userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
登入後複製
登入後複製
登入後複製
登入後複製

起初,這可能看起來有點令人困惑。但是,你越了解 RxJS,創建 Epic 就越容易。

與 saga 一樣,我們可以分發多個 action,每個 action 都描述線程當前位於 API 請求鏈的哪個部分。

就可擴展性而言,我們可以根據特定任務拆分 Epic 或組合 Epic。因此,此庫可以幫助構建可擴展的應用程序。如果我們理解編寫代碼的可觀察模式,則代碼清晰度很好。

我的偏好

如何確定使用哪個庫?這取決於我們的 API 請求有多複雜。

如何選擇 Redux-Saga 和 Redux-Observable 之間?這取決於學習生成器還是 RxJS。兩者都是不同的概念,但同樣足夠好。我建議嘗試兩者,看看哪個更適合你。

將處理 API 的業務邏輯放在哪裡?最好放在 reducer 之前,但不要放在組件中。最好的方法是在中間件中(使用 saga 或 observable)。

您可以在 Codebrahma 閱讀更多 React 開發文章。

關於 React-Redux 應用程序中異步操作的常見問題

中間件在處理 Redux 中的異步操作中的作用是什麼?

Redux 中的中間件在處理異步操作中起著至關重要的作用。它在分發 action 和 action 到達 reducer 之間提供了一個第三方擴展點。中間件可用於記錄、修改甚至取消 action,以及分發其他 action。在異步操作的上下文中,像 Redux Thunk 或 Redux Saga 這樣的中間件允許您編寫返回函數而不是 action 的 action 創建者。然後,可以使用此函數來延遲 action 的分發或僅在滿足特定條件時分發 action。

Redux Thunk 如何幫助管理異步操作?

Redux Thunk 是一個中間件,允許您編寫返回函數而不是 action 的 action 創建者。 thunk 可用於延遲 action 的分發或僅在滿足特定條件時分發 action。此功能使其成為處理 Redux 中異步操作的絕佳工具。例如,您可以分發一個 action 來指示 API 調用的開始,然後在調用返回數據或錯誤消息時分發另一個 action。

Redux Thunk 和 Redux Saga 之間的區別是什麼?

Redux Thunk 和 Redux Saga 都是用於管理 Redux 中副作用(包括異步操作)的中間件。兩者之間的主要區別在於它們的方法。 Redux Thunk 使用回調函數來處理異步操作,而 Redux Saga 使用生成器函數和更聲明式的方法。這使得 Redux Saga 更加強大和靈活,但也更加複雜。如果您的應用程序具有簡單的異步操作,Redux Thunk 可能就足夠了。但是,對於涉及競爭條件、取消和 if-else 邏輯的更複雜場景,Redux Saga 可能是更好的選擇。

如何在 Redux 中處理異步操作中的錯誤?

可以在異步操作期間發生錯誤時分發 action 來處理 Redux 中異步操作中的錯誤處理。此 action 可以將其錯誤消息作為其有效負載。然後,您可以在 reducer 中處理此 action 以使用錯誤消息更新狀態。這樣,可以向用戶顯示錯誤消息或將其記錄以進行調試。

如何在 Redux 中測試異步 action?

可以通過模擬 Redux 存儲和 API 調用來測試 Redux 中的異步 action。對於 Redux 存儲,您可以使用像 redux-mock-store 這樣的庫。對於 API 調用,您可以使用像 fetch-mocknock 這樣的庫。在您的測試中,您可以分發異步 action,然後斷言已使用正確的有效負載分發了預期的 action。

如何在 Redux 中取消異步操作?

可以使用像 Redux Saga 這樣的中間件來取消 Redux 中的異步操作。 Redux Saga 使用生成器函數,可以使用 cancel effect 來取消這些函數。當 cancel effect 被 yield 時,saga 將從其啟動點到當前 effect 被取消。

如何在 Redux 中處理異步操作中的競爭條件?

可以使用像 Redux Saga 這樣的中間件來處理 Redux 中異步操作中的競爭條件。 Redux Saga 提供了像 takeLatesttakeEvery 這樣的 effect,可用於處理並發 action。例如,如果在分發新 action 時,先前啟動的 saga 任務仍在運行,則 takeLatest 會取消該任務。

如何將 async/await 與 Redux Thunk 一起使用?

Redux Thunk 原生支持 async/await。在您的 action 創建者中,您可以返回異步函數而不是常規函數。在此異步函數內部,您可以使用 async/await 來處理異步操作。當異步操作完成時,可以使用 action 對象調用 dispatch 函數。

如何在 Redux 中的異步操作中處理加載狀態?

可以通過在異步操作之前和之後分發 action 來處理 Redux 中異步操作中的加載狀態。在操作之前分發的 action 可以將加載狀態設置為 true,而操作之後分發的 action 可以將其設置為 false。在您的 reducer 中,您可以處理這些 action 以更新存儲中的加載狀態。

如何在 Redux 中處理副作用?

可以使用像 Redux Thunk 或 Redux Saga 這樣的中間件來處理 Redux 中的副作用。這些中間件允許您編寫返回函數而不是 action 的 action 創建者。此函數可用於執行副作用,例如異步操作、記錄和有條件地分發 action。

以上是React Redux應用中的異步操作的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板