場(chǎng)景:參數(shù)特別多,從父組件一路往子孫組件傳遞fn搜集不合適
1. 新建管理state的文件,新建 導(dǎo)出 高階組件widthBarState 和自己的useBarState在組件中使用
// barState.tsx
import { createContext, FC, useReducer, useContext } from 'react'
import { addDaysStr } from '@/utils/date'
import { HotelParamsProps } from '@/typings/hotelProps'
type BarReducer = React.Reducer<HotelParamsProps, any>
const defaultValue: HotelParamsProps = {
type: 0,
keyWords: '',
}
const BarContext = createContext<{
paramsState: HotelParamsProps
dispatch?: React.Dispatch<any>
}>({
paramsState: defaultValue,
})
export const useBarState = () => {
return useContext(BarContext)
}
action也可以指定type類型區(qū)分操作,我這里偷懶直接傳數(shù)據(jù)
const reducer: BarReducer = (state: HotelParamsProps, action: any) => {
return {
...state,
...action,
}
}
export const widthBarState = (Com: FC<any>) => {
return (props: any) => {
const [paramsState, dispatch] = useReducer<BarReducer>(
reducer,
defaultValue,
)
return (
<BarContext.Provider value={{ paramsState, dispatch }}>
<Com {...props} />
</BarContext.Provider>
)
}
}
(1) 上面代碼我們仔細(xì)看這一部分。這樣寫沒(méi)問(wèn)題,但是大部分地方只需要執(zhí)行dispatch 就行,由于觸發(fā)dispatch ,導(dǎo)致paramsState變化了,只要使用了useContext (也就是自定義的useBarState)的地方都會(huì)更新。
這樣不好,因?yàn)橹粓?zhí)行dispatch的組件是不需要更新的
<BarContext.Provider value={{ paramsState, dispatch }}>
<Com {...props} />
</BarContext.Provider>
這樣拆分比較好,只需要用到dispatch的組件,觸發(fā)dispatch或者其他組件dispatch,都不會(huì)重新渲染。
import {
createContext,
FC,
useReducer,
useContext,
Dispatch,
SetStateAction,
} from 'react'
import { addDaysStr } from '@/utils/date'
import { HotelParamsProps } from '@/typings/hotelProps'
type BarReducer = React.Reducer<HotelParamsProps, any>
const defaultValue: HotelParamsProps = {
type: 0,
keyWords: '',
}
拆分Dispatch 和 defaultValue
const BarContext = createContext<HotelParamsProps>(defaultValue)
const DispatchBarContext = createContext<
Dispatch<SetStateAction<HotelParamsProps>> | undefined
>(undefined)
export const useBarState = () => {
return useContext(BarContext)
}
export const useDispatchBarState = () => {
return useContext(DispatchBarContext)
}
const reducer: BarReducer = (state: HotelParamsProps, action: any) => {
return {
...state,
...action,
}
}
export const widthBarState = (Com: FC<any>) => {
return (props: any) => {
const [paramsState, dispatch] = useReducer<BarReducer>(
reducer,
defaultValue,
)
return (
<BarContext.Provider value={paramsState}>
<DispatchBarContext.Provider value={dispatch}>
<Com {...props} />
</DispatchBarContext.Provider>
</BarContext.Provider>
)
}
}
(2)請(qǐng)注意,上面使用的是useReducer。其實(shí)使用useState也是可以的。在組件中使用useReducer管理組件的state也是可以的。
useReducer可以合并多個(gè)變量,當(dāng)然顆?;M件中不會(huì)存在那么多變量,useState足夠用了
2. 在組件中使用
(1)使用高階組件widthBarState 包裹頂層父組件,并在組件中使用useBarState,useDispatchBarState提取使用更新paramsState
const TicketListPage = () => {
const {
location: { state },
} = useHistory()
const paramsState = useBarState()
const dispatch = useDispatchBarState()
這里業(yè)務(wù)相關(guān)
useEffect(() => {
// 這里的paramsState只有keywords,type
const parmas = state
? Object.assign(paramsState, state)
: Object.assign(paramsState, ticketDefault)
dispatch && dispatch(parmas)
}, [])
return (
<div className={'container'}>
<SearchBar isHotel={false} />
<Scenics />
<Tags />
</div>
)
}
包裹頂層父組件,SearchBar, Scenics 組件中要搜集參數(shù)
export default widthBarState(TicketListPage)
3. 繼續(xù)優(yōu)化:使用 useMemo() 解決 state Context 透?jìng)鞯男阅軉?wèn)題
上面提到paramsState變化了,只要用到了useContext 的地方都會(huì)觸發(fā)更新。
但是管理的數(shù)據(jù)特別多的情況下,一個(gè)子組件僅僅需要用到部分state中的數(shù)據(jù),此時(shí)全部都要更新就顯得不必要了,要是能在paramsState變化后,只針對(duì)變化的數(shù)據(jù)更新組件就很完美了
const DateRange: FC = memo(() => {
const { type } = useBarState()
const dispatch = useDispatchBarState()
const handleTime = (val: any) => {
const [start, end] = val
const params = {
startDate: start.format('yyyy-MM-DD'),
endDate: end.format('yyyy-MM-DD'),
}
dispatch && dispatch(params)
}
return (
<>
<div className={styles.gap} />
<div className={'flex items-center'}>
<SpriteIcon name="calendar" className={styles.icon} />
<RangePicker
suffixIcon=""
onChange={handleTime}
bordered={false}
locale={locale}
/>
</div>
</>
)
})
以上代碼是一個(gè)RangePicker選擇組件,該組件只需要useBarState(也就是useContext)中的type,其他的不需要,理論上不需要的也不應(yīng)該觸發(fā)這個(gè)組件,所以應(yīng)該改造
改造如下
以前的memo也可以去掉了,之前寫memo是因?yàn)楦附M件有個(gè)setState操作
const DateRange: FC = () => {
const { type } = useBarState()
const dispatch = useDispatchBarState()
return useMemo(() => {
const handleTime = (val: any) => {
const [start, end] = val
const params = {
startDate: start.format('yyyy-MM-DD'),
endDate: end.format('yyyy-MM-DD'),
}
dispatch && dispatch(params)
}
return (
<>
<div className={styles.gap} />
<div className={'flex items-center'}>
<SpriteIcon name="calendar" className={styles.icon} />
<RangePicker
suffixIcon=""
onChange={handleTime}
bordered={false}
locale={locale}
/>
</div>
</>
)
}, [type])
}