跳到主要內容

Redux 常見問題:效能

目錄

效能

Redux 在效能和架構方面「擴充」的程度如何?

雖然沒有單一的明確答案,但在大多數情況下,這不應該是問題。

Redux 所做的工作通常分為幾個領域:處理中間件和 reducer 中的動作(包括物件複製以進行不變更新)、在動作發送後通知訂閱者,以及根據狀態變更更新 UI 元件。雖然在足夠複雜的情況下,這些中的每個可能成為效能問題,但 Redux 的實作方式並非本質上緩慢或低效率。事實上,React Redux 特別經過大量最佳化以減少不必要的重新渲染,而 React-Redux v5 顯示出比早期版本顯著的進步。

與其他程式庫相比,Redux 開箱即用的效率可能較低。為了在 React 應用程式中獲得最佳的渲染效能,狀態應儲存在正規化的形式中,許多個別元件應連接到儲存,而不要只有少數幾個,連接的清單元件應將項目 ID 傳遞給其連接的子清單項目(允許清單項目按 ID 查詢自己的資料)。這將整體要執行的渲染量降至最低。使用記憶化選擇器函式也是效能的重要考量因素。

在架構方面,有軼事證據表明 Redux 適用於不同專案和團隊規模。目前有數百家公司和數千名開發人員使用 Redux,每月從 NPM 安裝的次數達數十萬次。一位開發人員回報

就規模而言,我們有大約 500 個動作類型、400 個簡約器案例、150 個組件、5 個中間件、200 個動作、2300 個測試

進一步的資訊

文件

文章

討論

針對每個動作呼叫「所有簡約器」不會很慢嗎?

請務必注意,Redux 儲存區實際上只有一個簡約器函式。儲存區會將目前的狀態和發出的動作傳遞給該簡約器函式,並讓簡約器適當地處理事情。

顯然,嘗試在單一函式中處理所有可能的動作在函式大小和可讀性方面無法很好地擴充,因此將實際工作拆分為可由頂層簡約器呼叫的個別函式是有道理的。特別是,常見的建議模式是有一個負責管理特定金鑰特定狀態區塊更新的個別子簡約器函式。Redux 附帶的 combineReducers() 是達成此目的的許多可能方法之一。強烈建議盡可能保持儲存區狀態扁平且正規化。不過,最終由您負責以任何您想要的方式整理簡約器邏輯。

不過,即使你碰巧有許多不同的 reducer 函式組合在一起,而且狀態層級很深,reducer 的速度不太可能成為問題。JavaScript 引擎每秒可以執行大量的函式呼叫,而且你的大多數 reducer 可能只是使用 switch 陳述式,並在回應大多數動作時預設傳回現有的狀態。

如果你真的擔心 reducer 的效能,你可以使用像 redux-ignorereduxr-scoped-reducer 這樣的工具程式,以確保只有某些 reducer 會聆聽特定動作。你也可以使用 redux-log-slow-reducers 進行一些效能基準測試。

進一步資訊

討論

我必須在 reducer 中深度複製我的狀態嗎?複製我的狀態不會很慢嗎?

不可變地更新狀態通常表示進行淺層複製,而不是深度複製。淺層複製比深度複製快很多,因為必須複製的物件和欄位較少,而且實際上只是移動一些指標。

此外,深度複製狀態會為每個欄位建立新的參考。由於 React-Redux connect 函式依賴於參考比較來判斷資料是否已變更,這表示即使其他資料沒有實質變更,UI 元件仍會被迫不必要地重新渲染。

不過,你確實需要為受影響的每個層級建立一個複製且已更新的物件。雖然這不應該是特別昂貴,但這是你應該盡可能讓你的狀態正規化且淺層的另一個好理由。

Redux 常見的誤解:你需要深度複製狀態。現實:如果內部沒有變更,請保持其參考相同!

進一步資訊

文件

討論

如何減少儲存更新事件的數量?

Redux 會在每次成功發送動作後通知訂閱者(也就是動作已到達儲存,並已由簡化器處理)。在某些情況下,減少訂閱者被呼叫的次數可能很有用,特別是當動作建立器連續發送多個不同動作時。

有許多附加元件以各種方式新增批次處理功能,例如:redux-batched-actions(一個高階簡化器,讓您可以發送多個動作,就像只有一個動作,並在簡化器中「解壓縮」它們)、redux-batched-subscribe(一個儲存增強器,讓您可以針對多個發送動作,暫緩訂閱者呼叫)、或 redux-batch(一個儲存增強器,處理使用單一訂閱者通知發送動作陣列)。

特別針對 React-Redux,從 React-Redux v7 開始,有一個新的公開 API batch 可用,以協助在 React 事件處理常式之外發送動作時,將 React 重新渲染的次數減至最少。它包裝 React 的 unstable_batchedUpdate() API,允許事件迴圈滴答中的任何 React 更新批次處理成單一渲染傳遞。React 已在內部將其用於自己的事件處理常式回呼。此 API 實際上是渲染器套件(例如 ReactDOM 和 React Native)的一部分,而不是 React 核心本身。

由於 React-Redux 需要在 ReactDOM 和 React Native 環境中運作,因此我們已在建置時從正確的渲染器匯入此 API 以供我們自己使用。我們現在也重新公開匯出此函式,並重新命名為 batch()。您可以使用它來確保在 React 之外發送的多個動作只會產生單一渲染更新,如下所示

import { batch } from 'react-redux'

function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment())
dispatch(increment())
})
}
}

進一步資訊

討論

函式庫

擁有「單一狀態樹」會造成記憶體問題嗎?發送多個動作會佔用記憶體嗎?

首先,在原始記憶體使用方面,Redux 與任何其他 JavaScript 函式庫並無不同。唯一的差別在於所有不同的物件參考都巢狀在一起成為一個樹狀結構,而不是像 Backbone 那樣儲存在不同的獨立模型實例中。其次,典型的 Redux 應用程式可能比等效的 Backbone 應用程式有較少的記憶體使用量,因為 Redux 鼓勵使用純 JavaScript 物件和陣列,而不是建立模型和集合的實例。最後,Redux 一次只保留單一狀態樹參考。不再在該樹狀結構中被引用的物件將會像往常一樣被垃圾回收。

Redux 本身並不會儲存動作記錄。然而,Redux DevTools 會儲存動作,以便可以重播,但這些通常只在開發期間啟用,而不會在生產環境中使用。

進一步資訊

文件

討論

快取遠端資料會造成記憶體問題嗎?

在瀏覽器中執行的 JavaScript 應用程式可用的記憶體量是有限的。當快取大小接近可用記憶體量時,快取資料會造成效能問題。當快取資料特別大或會話特別長時,這往往會成為問題。雖然有必要了解這些問題的可能性,但這種認知不應阻礙你有效率地快取合理數量的資料。

以下列出幾種有效率地快取遠端資料的方法

首先,僅快取使用者需要的資料量。如果你的應用程式顯示記錄的頁面清單,你不一定需要快取整個集合。相反地,快取使用者可見的內容,並在使用者有(或即將有)立即需要更多資料時新增至該快取。

其次,如果可能,快取記錄的簡略形式。有時記錄包含與使用者無關的資料。如果應用程式不依賴這些資料,則可以從快取中省略它們。

第三,僅快取記錄的單一副本。當記錄包含其他記錄的副本時,這一點特別重要。為每個記錄快取一個唯一的副本,並將每個巢狀副本替換為參考。這稱為正規化。正規化是儲存關聯資料的首選方法,原因有幾個,包括有效率的記憶體使用。

進一步資訊

討論