跳到主要內容

連線:使用 mapStateToProps 萃取資料

當傳遞給 connect 的第一個引數,mapStateToProps 用於從 store 中選擇已連接的元件需要的資料部分。它通常簡稱作 mapState

  • 每次 store 的狀態變更時,都會呼叫它。
  • 它接收整個 store 狀態,並應該回傳這個元件所需資料的物件。

定義 mapStateToProps

mapStateToProps 應該定義為一個函式

function mapStateToProps(state, ownProps?)

它應該接收一個稱為 state 的第一個引數,一個稱為 ownProps 的第二個引數(可選),並回傳包含已連接元件所需資料的純粹物件。

這個函式應該傳遞給 connect,並會在 Redux store 狀態變更時,每次呼叫。如果您不希望訂閱 store,請傳遞 nullundefinedconnect,取代 mapStateToProps

無論 mapStateToProps 函式使用 function 關鍵字(function mapState(state) { } )撰寫,或使用箭頭函式(const mapState = (state) => { } )撰寫,並無不同 - 它們的運作方式相同。

引數

  1. 狀態
  2. ownProps(可選)

state

mapStateToProps 函式的第一個參數是 Redux store 的完整狀態(與呼叫 store.getState() 傳回的值相同)。由於這個緣故,第一個參數傳統上稱為 state。(雖然你可以為參數命名,但叫它 store 會是不正確的,它是「狀態值」,不是「store 實例」。)

mapStateToProps 函式總是至少傳入 state 來撰寫。

// TodoList.js

function mapStateToProps(state) {
const { todos } = state
return { todoList: todos.allIds }
}

export default connect(mapStateToProps)(TodoList)

ownProps(可選)

如果你的元件需要自訂屬性的資料才能從 store 取得資料,你可以用第二個參數 ownProps 來定義函式。此參數將包含 connect 產生之包裝元件提供的所有屬性。

// Todo.js

function mapStateToProps(state, ownProps) {
const { visibilityFilter } = state
// ownProps would look like { "id" : 123 }
const { id } = ownProps
const todo = getTodoById(state, id)

// component receives additionally:
return { todo, visibilityFilter }
}

// Later, in your application, a parent component renders:
<ConnectedTodo id={123} />
// and your component receives props.id, props.todo, and props.visibilityFilter

你不必在從 mapStateToProps 傳回的物件中加入 ownProps 的值。connect 會自動將這些不同的屬性來源合併為一組最終的屬性。

傳回

你的 mapStateToProps 函式應傳回 包含元件所需資料的純物件

  • 物件中的每個欄位將成為你的實際元件的屬性
  • 欄位中的值將用來判定你的元件是否需要重新渲染

例如

function mapStateToProps(state) {
return {
a: 42,
todos: state.todos,
filter: state.visibilityFilter,
}
}

// component will receive: props.a, props.todos, and props.filter

註:在需要更進一步控制渲染效能的高階情境中,mapStateToProps 也能傳回函式。在這種情況下,那個函式會作為某個元件實例的最終 mapStateToProps 使用。此舉能讓你針對每個實例執行記憶。詳情請參閱文件中的 進階用法:函式工廠 區段,以及 PR #279 以及它新增的測試。大部份應用程式從不需要這樣做。

使用指南

mapStateToProps 調整 store 的資料

mapStateToProps 函式可以,而且必須,做的事比 return state.someSlice 還多。**它們有責任根據該元件所需,「調整」store 資料的型態。**這可能包括以特定屬性名稱傳回值、結合來自 state 樹不同部分的資料片段,並以不同方式轉換 store 資料。

使用選擇器函式來萃取並轉換資料

我們強烈建議使用「選擇器」函式來協助將值萃取自狀態樹中特定位置的流程封裝。已暫存的選擇器函式也在提升應用程式效能中扮演關鍵角色(請參閱本頁中的下列各區段以及 進階用法:運算衍生資料 頁面,以取得有關為何及如何使用選擇器的更多詳細資料。)

mapStateToProps 函式應為快速

每當 store 發生變更,所有已連接元件的所有 mapStateToProps 函式都會執行。因此,您的 mapStateToProps 函式應盡可能快速執行。這也表示緩慢的 mapStateToProps 函式可能是應用程式的潛在瓶頸。

作為「重新調整資料形狀」觀念的一部分,mapStateToProps 函式時常需要透過各種方式來轉換資料(例如篩選陣列、將 ID 陣列對應至相應的物件,或從 Immutable.js 物件中萃取純粹的 JS 值)。這些轉換通常會帶來極高的花費,無論是執行轉換的花費還是元件重新渲染結果造成的花費。如果效能令人擔憂,請確保這些轉換僅在輸入值發生變更時才執行。

mapStateToProps 函式應為純粹且同步

mapStateToProps 函式應始終 100% 純粹且同步,非常類似 Redux reducer。它應僅將 state (和 ownProps) 作為引數,並且傳回元件所需資料作為 props,而不改變這些引數。它不應用於觸發資料擷取等非同步行為之 AJAX 呼叫,而且不應將函式宣告為 async

mapStateToProps 與效能

傳回值決定您的元件是否重新渲染

React Redux 內部實施了 shouldComponentUpdate 方法,使得封裝組件準確地在組件需要資料修改時重新呈現。預設上,React Redux 會決定從 mapStateToProps 傳回的物件內容是否有變,是透過在傳回物件的每個欄位上使用 === 比較(「浮面比較」檢查)來決定。如果任一欄位有變,那麼你的組件將會重新呈現,以便它可以將更新後的數值當成 props 收到。請注意,傳回相同參照的突變物件是個常見的錯誤,你的組件可能無法在預期中重新呈現。

總之,用 mapStateToProps 從 store 中提取資料並封裝在 connect 中的組件行為為:

(state) => stateProps(state, ownProps) => stateProps
mapStateToProps 執行時為:store state 更改時store state 更改時

ownProps 的任一欄位不同時
組件重新呈現時為:stateProps 的任一欄位不同時stateProps 的任一欄位不同時

ownProps 的任一欄位不同時

只在需要時傳回新物件參照

React Redux 使用浮面比較查看 mapStateToProps 結果是否改變。每次不小心傳回新的物件或陣列參照很簡單,即使資料實際上沒變,也會導致組件重新呈現。

許多常見操作會導致產生新的物件或陣列參照

  • 使用 someArray.map()someArray.filter() 產生新陣列
  • array.concat 合併陣列
  • array.slice 選擇陣列的部分
  • Object.assign 複製數值
  • 以延伸運算子 { ...oldState, ...newData } 複製數值

將這些操作配置在 備忘selector函式 中,確保只有輸入數值改變時才會執行。這也會確保如果輸入數值沒有改變,mapStateToProps 仍會傳回和先前相同的結果值,而 connect 可以略過重新呈現。

只在資料變更時執行昂貴的操作

轉換資料通常很昂貴(而且通常會產生新的物件參照)。為了讓你的 mapStateToProps 函式儘可能地快,只有在相關資料變更時才應該重新執行這些複雜的轉換。

有幾種方法可以著手進行這件事

  • 某些轉換可以在動作建立器或還原器中計算,而轉換後的資料可以儲存在儲存區中
  • 也可以在組件的 render() 方法中進行轉換
  • 如果轉換確實需要在 mapStateToProps 函式中進行,我們建議使用 備忘選取器函式 來確保只有在輸入值變更時才執行轉換。

Immutable.js 效能疑慮

Immutable.js 作者李拜倫在 Twitter 明確建議避免在效能受影響時使用 toJS

給 #immutablejs 的效能提示:避免使用 .toJS()、.toObject() 和 .toArray(),這些都是緩慢的全部複製操作,會讓結構共享變得無用。

使用 Immutable.js 時,還有其他幾個效能疑慮需要考量,有關更多資訊,請參閱本頁結尾的連結清單。

行為和注意事項

如果儲存區狀態相同,mapStateToProps 將不會執行

connect 所產生的包覆組件會訂閱 Redux 儲存區。每當派送動作時,它會呼叫 store.getState(),並檢查 lastState === currentState。如果兩個狀態值透過參照是相同的,那麼它不會重新執行您的 mapStateToProps 函式,因為它假設其他儲存區狀態也未變更。

Redux combineReducers 實用函式試圖對此進行最佳化。如果沒有任何切片還原器傳回新值,則 combineReducers 會傳回舊的狀態物件,而不是新的。這表示還原器中的變異可能會導致未更新根狀態物件,進而導致 UI 無法重新呈現。

宣告參數的數量會影響行為

只有 (state) 時,只要根儲存區狀態物件不同,函式就會執行。使用 (state, ownProps) 時,它會在儲存區狀態不同時執行,而且在包覆 prop 有變更時也會執行。

這表示您不要加入 ownProps 參數,除非您真的需要使用它,否則您的 mapStateToProps 函式將會比必要的執行得更頻繁。

這種行為有一些邊界情況。強制性參數的數量決定 mapStateToProps 是否會收到 ownProps

如果函式的正式定義包含一個強制性參數,則 mapStateToProps 不會 收到 ownProps

function mapStateToProps(state) {
console.log(state) // state
console.log(arguments[1]) // undefined
}
const mapStateToProps = (state, ownProps = {}) => {
console.log(state) // state
console.log(ownProps) // {}
}

當函式的正式定義包含零個或兩個強制性參數時,它收到 ownProps

function mapStateToProps(state, ownProps) {
console.log(state) // state
console.log(ownProps) // ownProps
}

function mapStateToProps() {
console.log(arguments[0]) // state
console.log(arguments[1]) // ownProps
}

function mapStateToProps(...args) {
console.log(args[0]) // state
console.log(args[1]) // ownProps
}

教學

效能

問與答