优化使用useContext + useReducer 做数据管理

场景:参数特别多,从父组件一路往子孙组件传递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])
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容