關鍵要點
此文章最初發表於 Codebrahma。
JavaScript 是一種單線程編程語言。也就是說,當你編寫如下代碼時……
……第二行只有在第一行執行完畢後才會執行。大多數情況下這不會有問題,因為客戶端或服務器每秒執行數百萬次計算。只有當我們執行代價高昂的計算(需要相當長的時間才能完成的任務——網絡請求需要一些時間才能返回)時,我們才會注意到這些影響。
為什麼我只在這裡展示 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 調用:
userDetails
(第一個 API 響應)中獲取 city假設端點是 /details
。它將在響應中包含城市。響應將是一個對象:
userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
假設端點是 /restuarants/:city
。響應將是一個數組:
userDetails: { … city: 'city', … };
請記住,我們只有在完成第一個請求後才能發出第二個請求(因為它依賴於第一個請求)。讓我們看看各種方法:
setState
我特別選擇了上述方法,因為它們是大型項目中最常用的方法。仍然存在其他方法可能更特定於特定任務,並且不具備複雜應用程序所需的所有功能(例如 redux-async、redux-promise、redux-async-queue)。
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, // 设置状态 }) }) }) }
這兩種方法都是最簡單的。由於整個邏輯都在組件內部,因此我們可以很容易地在組件加載後一次性獲取所有數據。
當進行基於數據的複雜交互時,問題就會出現。例如,考慮以下情況:
狀態管理 在這些情況下,使用全局存儲實際上可以解決我們一半的問題。我們將使用 Redux 作為我們的全局存儲。
將業務邏輯移動到正確的位置 如果我們考慮將業務邏輯移出組件,那麼我們究竟可以在哪裡這樣做?在 actions 中?在 reducers 中?通過中間件? Redux 的架構是同步的。一旦你分發一個 action(JS 對象)並且它到達存儲,reducer 就會對其進行操作。
確保有一個單獨的線程執行異步代碼,並且可以通過訂閱檢索對全局狀態的任何更改
由此,我們可以了解到,如果我們將所有獲取邏輯移動到 reducer 之前——即 action 或中間件——那麼就可以在正確的時間分發正確的 action。例如,一旦獲取開始,我們可以分發 dispatch({ type: 'FETCH_STARTED' })
,當它完成時,我們可以分發 dispatch({ type: 'FETCH_SUCCESS' })
。
想要開發一個 React JS 應用程序?
Redux Thunk 是 Redux 的中間件。它基本上允許我們返回函數而不是對像作為 action。這通過提供 dispatch
和 getState
作為函數的參數來提供幫助。我們有效地使用 dispatch
在正確的時間分發必要的 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 是基於 ES6 生成器開發的。
Redux-Saga 提供了一個 API,有助於實現以下目標:
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 有益:
正如其文檔中“Epic 是 redux-observable 的核心原語”部分所述:
對於我們的任務,我們可以簡單地編寫如下代碼:
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 開發文章。
Redux 中的中間件在處理異步操作中起著至關重要的作用。它在分發 action 和 action 到達 reducer 之間提供了一個第三方擴展點。中間件可用於記錄、修改甚至取消 action,以及分發其他 action。在異步操作的上下文中,像 Redux Thunk 或 Redux Saga 這樣的中間件允許您編寫返回函數而不是 action 的 action 創建者。然後,可以使用此函數來延遲 action 的分發或僅在滿足特定條件時分發 action。
Redux Thunk 是一個中間件,允許您編寫返回函數而不是 action 的 action 創建者。 thunk 可用於延遲 action 的分發或僅在滿足特定條件時分發 action。此功能使其成為處理 Redux 中異步操作的絕佳工具。例如,您可以分發一個 action 來指示 API 調用的開始,然後在調用返回數據或錯誤消息時分發另一個 action。
Redux Thunk 和 Redux Saga 都是用於管理 Redux 中副作用(包括異步操作)的中間件。兩者之間的主要區別在於它們的方法。 Redux Thunk 使用回調函數來處理異步操作,而 Redux Saga 使用生成器函數和更聲明式的方法。這使得 Redux Saga 更加強大和靈活,但也更加複雜。如果您的應用程序具有簡單的異步操作,Redux Thunk 可能就足夠了。但是,對於涉及競爭條件、取消和 if-else 邏輯的更複雜場景,Redux Saga 可能是更好的選擇。
可以在異步操作期間發生錯誤時分發 action 來處理 Redux 中異步操作中的錯誤處理。此 action 可以將其錯誤消息作為其有效負載。然後,您可以在 reducer 中處理此 action 以使用錯誤消息更新狀態。這樣,可以向用戶顯示錯誤消息或將其記錄以進行調試。
可以通過模擬 Redux 存儲和 API 調用來測試 Redux 中的異步 action。對於 Redux 存儲,您可以使用像 redux-mock-store
這樣的庫。對於 API 調用,您可以使用像 fetch-mock
或 nock
這樣的庫。在您的測試中,您可以分發異步 action,然後斷言已使用正確的有效負載分發了預期的 action。
可以使用像 Redux Saga 這樣的中間件來取消 Redux 中的異步操作。 Redux Saga 使用生成器函數,可以使用 cancel
effect 來取消這些函數。當 cancel
effect 被 yield 時,saga 將從其啟動點到當前 effect 被取消。
可以使用像 Redux Saga 這樣的中間件來處理 Redux 中異步操作中的競爭條件。 Redux Saga 提供了像 takeLatest
和 takeEvery
這樣的 effect,可用於處理並發 action。例如,如果在分發新 action 時,先前啟動的 saga 任務仍在運行,則 takeLatest
會取消該任務。
Redux Thunk 原生支持 async/await。在您的 action 創建者中,您可以返回異步函數而不是常規函數。在此異步函數內部,您可以使用 async/await 來處理異步操作。當異步操作完成時,可以使用 action 對象調用 dispatch
函數。
可以通過在異步操作之前和之後分發 action 來處理 Redux 中異步操作中的加載狀態。在操作之前分發的 action 可以將加載狀態設置為 true,而操作之後分發的 action 可以將其設置為 false。在您的 reducer 中,您可以處理這些 action 以更新存儲中的加載狀態。
可以使用像 Redux Thunk 或 Redux Saga 這樣的中間件來處理 Redux 中的副作用。這些中間件允許您編寫返回函數而不是 action 的 action 創建者。此函數可用於執行副作用,例如異步操作、記錄和有條件地分發 action。
以上是React Redux應用中的異步操作的詳細內容。更多資訊請關注PHP中文網其他相關文章!