跳到主要內容

Redux 常見問題:React Redux

目錄

React Redux

為什麼我應該使用 React-Redux?

Redux 本身是一個獨立的函式庫,可與任何 UI 層或架構搭配使用,包括 React、Angular、Vue、Ember 和純 JS。儘管 Redux 和 React 通常一起使用,但它們是獨立的。

如果您將 Redux 與任何類型的 UI 架構搭配使用,您通常會使用「UI 繫結」函式庫將 Redux 與您的 UI 架構綁定在一起,而不是直接從您的 UI 程式碼與儲存體互動。

React-Redux 是 React 的官方 Redux UI 繫結函式庫。如果您將 Redux 和 React 一起使用,您也應該使用 React-Redux 來繫結這兩個函式庫。

雖然可以手動撰寫 Redux 儲存體訂閱邏輯,但這樣做會變得非常重複。此外,最佳化 UI 效能需要複雜的邏輯。

訂閱儲存體、檢查更新資料和觸發重新呈現的流程可以變得更通用且可重複使用。像 React-Redux 這樣的 UI 繫結函式庫會處理儲存體互動邏輯,因此您不必自己撰寫該程式碼。

總的來說,React-Redux 鼓勵良好的 React 架構,並為您實作複雜的效能最佳化。它也會持續更新,以符合 Redux 和 React 的最新 API 變更。

進一步資訊

文件

為何我的元件沒有重新呈現,或我的 mapStateToProps 沒有執行?

意外地變異或直接修改你的狀態是到目前為止最常見的原因,導致元件在動作分派後沒有重新渲染。Redux 預期你的 reducer 會「不可變地」更新它們的狀態,這有效地表示總是製作你資料的副本,並將你的變更套用至副本。如果你從 reducer 傳回同一個物件,Redux 會假設沒有任何變更,即使你對其內容進行變更。類似地,React Redux 嘗試透過在 shouldComponentUpdate 中對傳入的 props 進行淺層相等性參考檢查來改善效能,如果所有參考都相同,shouldComponentUpdate 會傳回 false 以略過實際更新你的原始元件。

重要的是要記住,每當你更新巢狀值時,你還必須傳回你狀態樹中其上方任何項目的新副本。如果你有 state.a.b.c.d,而且你想要對 d 進行更新,你也需要傳回 cbastate 的新副本。這個 狀態樹變異圖示 說明了樹狀結構深處的變更需要一路變更到最上方。

請注意,「不可變地更新資料」並不表示你必須使用 Immer,儘管那當然是一個選項。你可以使用數種不同的方法對純 JS 物件和陣列進行不可變更新

  • 使用 Object.assign()_.extend() 等函數複製物件,以及使用 slice()concat() 等陣列函數
  • ES2015 中的陣列擴散運算子,以及 ES2018 中類似的物件擴散運算子
  • 將不可變更新邏輯包裝成更簡單函數的公用程式函式庫

進一步資訊

文件

文章

討論

為什麼我的元件重新渲染的頻率太高?

React Redux 實作了多項最佳化,以確保您的實際元件只在必要時才重新渲染。其中一項是對傳遞給 connectmapStateToPropsmapDispatchToProps 參數所產生的合併 props 物件進行淺層相等性檢查。遺憾的是,在每次呼叫 mapStateToProps 時都會建立新的陣列或物件實例的情況下,淺層相等性檢查無法提供幫助。一個典型的範例可能是對 ID 陣列進行對應,並傳回對應的物件參考,例如

const mapStateToProps = state => {
return {
objects: state.objectIds.map(id => state.objects[id])
}
}

儘管陣列每次都可能包含完全相同的物件參考,但陣列本身是一個不同的參考,因此淺層相等性檢查會失敗,而 React Redux 會重新渲染包裝的元件。

額外的重新渲染可以透過使用還原器將物件陣列儲存到狀態中、使用 Reselect 快取對應的陣列,或手動在元件中實作 shouldComponentUpdate,並使用 _.isEqual 等函數進行更深入的 props 比較來解決。小心不要讓自訂的 shouldComponentUpdate() 比渲染本身更耗費資源!務必使用分析器來檢查您對效能的假設。

對於未連接的元件,您可能需要檢查傳入的 props 為何。一個常見的問題是父元件在其渲染函數中重新繫結一個回呼,例如 <Child onClick={this.handleClick.bind(this)} />。這會在父元件每次重新渲染時建立一個新的函數參考。一般來說,在父元件的建構函式中只繫結一次回呼會比較好。

進一步資訊

文件

文章

討論

函式庫

如何加快我的 mapStateToProps

雖然 React Redux 確實會盡量減少呼叫 mapStateToProps 函式的次數,但確保 mapStateToProps 快速執行並盡量減少其執行的作業量仍然是個好主意。常見的建議方法是使用 Reselect 建立經過記憶化的「選擇器」函式。這些選擇器可以組合在一起,而管線中後面的選擇器只會在輸入值變更時執行。這表示您可以建立執行篩選或排序等作業的選擇器,並確保只有在需要時才會執行實際作業。

進一步資訊

文件

文章

討論

為什麼我的連接元件中沒有 this.props.dispatch 可用?

connect() 函式有兩個主要參數,兩個都是選用的。第一個參數 mapStateToProps 是您提供的函式,用於在儲存資料變更時從儲存資料中提取資料,並將這些值作為道具傳遞給您的元件。第二個參數 mapDispatchToProps 是您提供的函式,用於使用儲存資料的 dispatch 函式,通常透過建立動作建立程式的預先繫結版本,以便在呼叫這些版本時自動派送其動作。

如果您在呼叫 connect() 時未提供自己的 mapDispatchToProps 函式,React Redux 將會提供一個預設版本,它會簡單地傳回 dispatch 函式作為一個 prop。這表示如果您提供自己的函式,則不會自動提供 dispatch。如果您仍希望它可用作 prop,則需要在您的 mapDispatchToProps 實作中明確地自行傳回它。

進一步的資訊

文件

討論

我應該只連接我的頂層元件,還是可以連接我的樹狀結構中的多個元件?

早期 Redux 文件建議您只在元件樹狀結構的頂端附近有幾個已連接的元件。然而,時間和經驗表明,這種元件架構通常需要幾個元件知道其所有後代的資料需求太多,並迫使它們傳遞令人困惑的 prop 數量。

目前建議的最佳做法是將您的元件分類為「展示型」或「容器型」元件,並在有意義的地方提取已連接的容器元件

在 Redux 範例中強調「頂端的單一容器元件」是一個錯誤。不要將此視為格言。嘗試將您的展示元件分開。在方便時透過連接建立容器元件。每當您覺得自己在父元件中重複程式碼以提供相同類型子項目的資料時,就是提取容器的時候了。通常,只要您覺得父元件對其子項目的「個人」資料或動作了解太多時,就是提取容器的時候了。

事實上,基準測試顯示,與較少的已連接元件相比,更多的已連接元件通常會帶來更好的效能。

一般來說,請嘗試在元件中,找出可理解的資料流程與責任區塊之間的平衡。

更多資訊

文件

文章

討論

Redux 與 React Context API 有何不同?

相似之處

Redux 和 React 的 Context API 都處理「prop 傳遞」。也就是說,它們都允許您傳遞資料,而無需透過多層元件傳遞 prop。內部而言,Redux 使用 React context API,允許它沿著元件樹傳遞儲存資料。

差異

使用 Redux,您可以獲得 Redux Dev Tools Extension 的強大功能。它會自動記錄應用程式執行的每個動作,並允許時間旅行 - 您可以按一下任何過去的動作,並跳到那個時間點。Redux 也支援中間件的概念,您可以在每個動作調度上繫結自訂函式呼叫。此類範例包括自動事件記錄器、攔截某些動作等。

使用 React 的 Context API,您處理的是一對僅彼此通訊的元件。這讓您在不相關資料之間有良好的隔離。您也可以彈性地使用資料與元件,也就是說,您可以提供父元件的狀態,並可以將內容資料作為 prop 傳遞給包裝的元件。

Redux 和 React 的 Context 在處理資料的方式上有個關鍵差異。Redux 在一個巨大的有狀態物件中維護整個應用程式的資料。它透過執行您提供的 reducer 函式來推論資料的變更,並傳回與每個調度動作相應的下一個狀態。然後 React Redux 最佳化元件渲染,並確保每個元件僅在它所需的資料變更時重新渲染。另一方面,Context 沒有持有任何狀態。它只是一個資料的管道。若要表達資料變更,您需要依賴父元件的狀態。

更多資訊