連線:使用 mapStateToProps
萃取資料
當傳遞給 connect
的第一個引數,mapStateToProps
用於從 store 中選擇已連接的元件需要的資料部分。它通常簡稱作 mapState
。
- 每次 store 的狀態變更時,都會呼叫它。
- 它接收整個 store 狀態,並應該回傳這個元件所需資料的物件。
定義 mapStateToProps
mapStateToProps
應該定義為一個函式
function mapStateToProps(state, ownProps?)
它應該接收一個稱為 state
的第一個引數,一個稱為 ownProps
的第二個引數(可選),並回傳包含已連接元件所需資料的純粹物件。
這個函式應該傳遞給 connect
,並會在 Redux store 狀態變更時,每次呼叫。如果您不希望訂閱 store,請傳遞 null
或 undefined
到 connect
,取代 mapStateToProps
。
無論 mapStateToProps
函式使用 function
關鍵字(function mapState(state) { }
)撰寫,或使用箭頭函式(const mapState = (state) => { }
)撰寫,並無不同 - 它們的運作方式相同。
引數
狀態
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
}
連結與參考資料
教學
效能
問與答