场景:参数特别多,从父组件一路往子孙组件传递fn搜集不合适
1. 新建管理state的文件,新建 导出 高阶组件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类型区分操作,我这里偷懒直接传数据
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) 上面代码我们仔细看这一部分。这样写没问题,但是大部分地方只需要执行dispatch 就行,由于触发dispatch ,导致paramsState变化了,只要使用了useContext (也就是自定义的useBarState)的地方都会更新。
这样不好,因为只执行dispatch的组件是不需要更新的
<BarContext.Provider value={{ paramsState, dispatch }}>
<Com {...props} />
</BarContext.Provider>
这样拆分比较好,只需要用到dispatch的组件,触发dispatch或者其他组件dispatch,都不会重新渲染。
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)请注意,上面使用的是useReducer。其实使用useState也是可以的。在组件中使用useReducer管理组件的state也是可以的。
useReducer可以合并多个变量,当然颗粒化组件中不会存在那么多变量,useState足够用了
2. 在组件中使用
(1)使用高阶组件widthBarState 包裹顶层父组件,并在组件中使用useBarState,useDispatchBarState提取使用更新paramsState
const TicketListPage = () => {
const {
location: { state },
} = useHistory()
const paramsState = useBarState()
const dispatch = useDispatchBarState()
这里业务相关
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 组件中要搜集参数
export default widthBarState(TicketListPage)
3. 继续优化:使用 useMemo() 解决 state Context 透传的性能问题
上面提到paramsState变化了,只要用到了useContext 的地方都会触发更新。
但是管理的数据特别多的情况下,一个子组件仅仅需要用到部分state中的数据,此时全部都要更新就显得不必要了,要是能在paramsState变化后,只针对变化的数据更新组件就很完美了
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>
</>
)
})
以上代码是一个RangePicker选择组件,该组件只需要useBarState(也就是useContext)中的type,其他的不需要,理论上不需要的也不应该触发这个组件,所以应该改造
改造如下
以前的memo也可以去掉了,之前写memo是因为父组件有个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])
}