Connect: Extracting Data with mapStateToProps
- mapStateToProps 返回 state 中所需的最少數(shù)據(jù)
- mapStateToProps 的返回值變化,組件會重新渲染
- mapStateToProps可以重新組裝數(shù)據(jù)
- mapStateToProps 不應(yīng)做耗時(shí)操作。耗時(shí)操作可以嘗試在 action creator 、reduce 、render 中實(shí)現(xiàn),如果確實(shí)需要在 mapStateToProps 中實(shí)現(xiàn),可以考慮使用 可記憶的 selector, 例如 reselect
- 如果需要返回引用對象,一定返回新對象
- 也可以返回函數(shù) (state, [props]) => object ,此時(shí)每一個(gè) connect 的實(shí)例對象,都會調(diào)用一次 makeMapStateToProps 函數(shù)
Connect: Dispatching Actions with mapDispatchToProps
- 不傳這個(gè)參數(shù),默認(rèn)會將
dispatch添加到 props 中。如果傳了,dispatch默認(rèn)不會傳入 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)每一個(gè)字段都是對象的時(shí)候,redux 會假定這個(gè)對象就是一個(gè) action creator。但是此時(shí) 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 特性實(shí)現(xiàn)向深層嵌套的組件傳遞 redux store 的。React redux version 6 中,context 是 ReactReduxContext ,由 React.createContext() 創(chuàng)建。
<Provider> 實(shí)際是 <ReactReduxContext.Provider>
connect 實(shí)際是用 <ReactReduxContext.Consumer> 獲取數(shù)據(jù) - 自定義 context
- 多個(gè) 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ù),默認(rèn)都是淺比較
{
context?: Object,
pure?: boolean,
areStatesEqual?: Function,
areOwnPropsEqual?: Function,
areStatePropsEqual?: Function,
areMergedPropsEqual?: Function,
forwardRef?: boolean,
}
Provider
connectAdvanced
connect 的一個(gè)底層實(shí)現(xiàn),一般用不到。
batch()
組合多個(gè) store 的改變在一個(gè)事件循環(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 ,在同一個(gè)組件中一個(gè) action 導(dǎo)致的多個(gè) useSelector 只會導(dǎo)致一次重繪。
- useSelector 應(yīng)執(zhí)行快,避免耗時(shí)操作
- 具有緩存機(jī)制,比較機(jī)制是 ===
- 可以手動傳入比較機(jī)制
import { shallowEqual, useSelector } from 'react-redux'
// later
const selectedData = useSelector(selectorReturningObject, shallowEqual)
- 配合 reselect 使用時(shí),當(dāng)一個(gè) selector 只在一個(gè)組件中使用時(shí),確保 selector 是同一個(gè)實(shí)例;當(dāng) selector 要在多個(gè)組件或者同個(gè)組件的多個(gè)實(shí)例中使用時(shí),確保 selector 是多個(gè)實(shí)例。因?yàn)?reselect 的 selector 是根據(jù)參數(shù)的變化來緩存計(jì)算結(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
- 一個(gè)利用 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>
))
這個(gè)例子中,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é)點(diǎn))
性能
使用 connect() 的組件,如果 state 和 props 都沒有改變,即使父組件重新渲染,子組件也不會重新渲染。但是 useSelector 的函數(shù)組件,子組件會隨父組件一起重新渲染,即使 state 和 props 都沒有改變。
為了更好的性能優(yōu)化,我們可以使用 React.memo 來解決這個(gè)問題:
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)
}