优化使用useContext + useReducer 做数据管

2021-07-20  本文已影响0人  云鹤道人张业斌

场景:参数特别多,从父组件一路往子孙组件传递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])
}
上一篇 下一篇

猜你喜欢

热点阅读