Connect: Extracting Data with mapStateToProps
- mapStateToProps 返回 state 中所需的最少數(shù)據(jù)
- mapStateToProps 的返回值變化蝌麸,組件會重新渲染
- mapStateToProps可以重新組裝數(shù)據(jù)
- mapStateToProps 不應(yīng)做耗時操作。耗時操作可以嘗試在 action creator 抡谐、reduce 、render 中實現(xiàn)呀页,如果確實需要在 mapStateToProps 中實現(xiàn),可以考慮使用 可記憶的 selector爆阶, 例如 reselect
- 如果需要返回引用對象斥扛,一定返回新對象
- 也可以返回函數(shù) (state, [props]) => object ,此時每一個 connect 的實例對象食茎,都會調(diào)用一次 makeMapStateToProps 函數(shù)
Connect: Dispatching Actions with mapDispatchToProps
- 不傳這個參數(shù)蒂破,默認會將
dispatch
添加到 props 中。如果傳了别渔,dispatch
默認不會傳入 props 中附迷。如果需要,需顯式返回哎媚。 - mapDispatchToProps 支持 Function 和 Object 兩種形式喇伯。推薦使用 plain object ,除非有特殊情況拨与。
- 不建議在 component 中直接調(diào)用 store.dispatch 或者通過 context 直接獲取 dispatch
函數(shù)形式:
const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })
const mapDispatchToProps = dispatch => {
return {
// dispatching actions returned by action creators
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement()),
reset: () => dispatch(reset())
}
}
bindActionCreators 形式:
import { bindActionCreators } from 'redux'
// ...
function mapDispatchToProps(dispatch) {
return bindActionCreators({ increment, decrement, reset }, dispatch)
}
// component receives props.increment, props.decrement, props.reset
connect(
null,
mapDispatchToProps
)(Counter)
Plain Object 形式:
當(dāng)每一個字段都是對象的時候稻据,redux 會假定這個對象就是一個 action creator。但是此時 props 中就沒有 dispatch 了买喧。
import {increment, decrement, reset} from "./counterActions";
const actionCreators = {
increment,
decrement,
reset
}
export default connect(mapState, actionCreators)(Counter);
// or
export default connect(
mapState,
{ increment, decrement, reset }
)(Counter);
如何獲取 Store
- 使用 useStore Hook
- 理解 Context 的使用
react-redux 是通過 react 的 context 特性實現(xiàn)向深層嵌套的組件傳遞 redux store 的捻悯。React redux version 6 中,context 是 ReactReduxContext 淤毛,由 React.createContext() 創(chuàng)建今缚。
<Provider> 實際是 <ReactReduxContext.Provider>
connect 實際是用 <ReactReduxContext.Consumer> 獲取數(shù)據(jù) - 自定義 context
- 多個 store
- 直接使用 ReactReduxContext
import { ReactReduxContext } from 'react-redux'
// in your connected component
function MyConnectedComponent() {
return (
<ReactReduxContext.Consumer>
{({ store }) => {
// do something useful with the store, like passing it to a child
// component where it can be used in lifecycle methods
}}
</ReactReduxContext.Consumer>
)
}
-----------API
connect
可以自定義 compare 函數(shù),默認都是淺比較
{
context?: Object,
pure?: boolean,
areStatesEqual?: Function,
areOwnPropsEqual?: Function,
areStatePropsEqual?: Function,
areMergedPropsEqual?: Function,
forwardRef?: boolean,
}
Provider
connectAdvanced
connect 的一個底層實現(xiàn),一般用不到低淡。
batch()
組合多個 store 的改變在一個事件循環(huán)中姓言,所以 UI 只重繪一次
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())
})
}
}
Hooks
- useSelector
- 從 React Redux v7 開始,由于使用了 batch behavior 查牌,在同一個組件中一個 action 導(dǎo)致的多個 useSelector 只會導(dǎo)致一次重繪事期。
- useSelector 應(yīng)執(zhí)行快滥壕,避免耗時操作
- 具有緩存機制纸颜,比較機制是 ===
- 可以手動傳入比較機制
import { shallowEqual, useSelector } from 'react-redux'
// later
const selectedData = useSelector(selectorReturningObject, shallowEqual)
- 配合 reselect 使用時,當(dāng)一個 selector 只在一個組件中使用時绎橘,確保 selector 是同一個實例胁孙;當(dāng) selector 要在多個組件或者同個組件的多個實例中使用時,確保 selector 是多個實例称鳞。因為 reselect 的 selector 是根據(jù)參數(shù)的變化來緩存計算結(jié)果的涮较。
import React, { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
const makeSelectCompletedTodosCount = () =>
createSelector(
state => state.todos,
(_, completed) => completed,
(todos, completed) =>
todos.filter(todo => todo.completed === completed).length
)
export const CompletedTodosCount = ({ completed }) => {
const selectCompletedTodosCount = useMemo(makeSelectCompletedTodosCount, [])
const matchingCount = useSelector(state =>
selectCompletedTodosCount(state, completed)
)
return <div>{matchingCount}</div>
}
export const App = () => {
return (
<>
<span>Number of done todos:</span>
<CompletedTodosCount completed={true} />
<span>Number of unfinished todos:</span>
<CompletedTodosCount completed={false} />
</>
)
}
- useDispatch
- 只要 store 對象不變,useDispatch 的返回值 dispatch 就不會變冈止。一般來說狂票,在應(yīng)用中,是不變的熙暴。但 React hooks lint rules 不知道闺属,所以 dependency 里面可以加上 dispatch
- 一個利用 useMemo 的優(yōu)化示例
import React, { useCallback } from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
const incrementCounter = useCallback(
() => dispatch({ type: 'increment-counter' }),
[dispatch]
)
return (
<div>
<span>{value}</span>
<MyIncrementButton onIncrement={incrementCounter} />
</div>
)
}
export const MyIncrementButton = React.memo(({ onIncrement }) => (
<button onClick={onIncrement}>Increment counter</button>
))
這個例子中慌盯,React.useMemo 的使用可以避免 onIncrement 的變化導(dǎo)致 MyIncrementButton 的重繪。
- useStore
這應(yīng)該盡量少用掂器,優(yōu)先考慮 useSelector - 自定義 context 的相關(guān) hooks 的使用
createStoreHook(MyContext)
createDispatchHook(MyContext)
createDispatchHook(MyContext)
警告
useSelector 可能會存在 Stale Props and "Zombie Children" 的問題亚皂。(舊的 Props 和 僵尸子節(jié)點)
性能
使用 connect() 的組件,如果 state 和 props 都沒有改變国瓮,即使父組件重新渲染灭必,子組件也不會重新渲染。但是 useSelector 的函數(shù)組件乃摹,子組件會隨父組件一起重新渲染禁漓,即使 state 和 props 都沒有改變。
為了更好的性能優(yōu)化峡懈,我們可以使用 React.memo 來解決這個問題:
const CounterComponent = ({ name }) => {
const counter = useSelector(state => state.counter)
return (
<div>
{name}: {counter}
</div>
)
}
export const MemoizedCounterComponent = React.memo(CounterComponent)
Hooks Recipes
- useActions:bindActionCreators 在函數(shù)組件中的替代璃饱,需要自己復(fù)制黏貼定義。
import { bindActionCreators } from 'redux'
import { useDispatch } from 'react-redux'
import { useMemo } from 'react'
export function useActions(actions, deps) {
const dispatch = useDispatch()
return useMemo(
() => {
if (Array.isArray(actions)) {
return actions.map(a => bindActionCreators(a, dispatch))
}
return bindActionCreators(actions, dispatch)
},
deps ? [dispatch, ...deps] : [dispatch]
)
}
- useShallowEqualSelector
import { useSelector, shallowEqual } from 'react-redux'
export function useShallowEqualSelector(selector) {
return useSelector(selector, shallowEqual)
}