優(yōu)化使用useContext + useReducer 做數(shù)據(jù)管理

場(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])
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容