🏡️ React-Query μ œλŒ€λ‘œ μ‚¬μš©ν•΄λ³΄κΈ° (2) 데이터 λ³€ν˜•ν•΄λ³΄κΈ°

JaneΒ·2023λ…„ 11μ›” 11일
3
post-thumbnail

πŸ€” μ°Ύμ•„λ³΄κ²Œ 된 계기

토이 ν”„λ‘œμ νŠΈμ—μ„œ Open APIμ—μ„œ 뢈러온 데이터λ₯Ό ν•„ν„°λ§ν•˜λŠ” λ‘œμ§μ„ κ΅¬ν˜„ν•˜κ²Œ λ˜μ—ˆλ‹€.

μœ„μ™€ 같은 ν˜•νƒœμ˜ μ»΄ν¬λ„ŒνŠΈμ˜€λŠ”λ°, μ–΄λ–»κ²Œ 필터값을 λ°˜μ˜ν•΄μ•Ό ν•  지에 λŒ€ν•΄ 고민이 λ§Žμ•˜λ‹€.
λ¬Όλ‘  필터링 μ’…λ₯˜κ°€ λ§Žμ•„μ„œλ„ λ§žμ§€λ§Œ, κ°€μž₯ κ³ λ―Όμ΄μ—ˆλ˜ 점은
Open APIμ—μ„œ 값을 λ°›μ•„μ˜€κΈ° λ•Œλ¬Έμ— μ›ν•˜λŠ” ν•„ν„° 값을 ν”„λ‘ νŠΈμ—μ„œ 직접 κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€λŠ” μ μ΄μ—ˆλ‹€.
μ–΄λ–»κ²Œ ν•˜λ©΄ ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ 필터링 λ˜μ§€ μ•Šμ€ μ±„λ‘œ 받은 응닡 값을 μ •μ œν•  λ•Œ,
μ΅œμ†Œν•œμ˜ API μš”μ²­κ³Ό μ΅œλŒ€ν•œμ˜ 퍼포먼슀λ₯Ό λ‚Ό 수 μžˆμ„κΉŒ?
이에 λŒ€ν•΄ react-query의 데이터λ₯Ό λ³€ν˜•ν•˜λŠ” 쒋은 방법듀이 μžˆμ–΄μ„œ μ •λ¦¬ν•΄λ³΄κ³ μž ν•œλ‹€.

πŸ‘©β€πŸ’» κ°€λŠ₯ν•˜λ‹€λ©΄ λ°±μ—”λ“œμ—μ„œ μ²˜λ¦¬ν•˜λŠ” 것도 쒋은 λ°©λ²•μž…λ‹ˆλ‹€.!

데이터λ₯Ό λ³€ν˜•ν•  수 μžˆλŠ” 방법듀

🟠 queryFnμ—μ„œ κ΅¬ν˜„ν•˜κΈ°

  • queryFn
    - useQuery에 전달해야 ν•˜λŠ” ν•¨μˆ˜μ΄λ‹€.
    • Promise 값을 λ°˜ν™˜ν•  것을 μ˜ˆμƒν•˜λ©°, κ²°κ³Ό 값은 query cache에 μ €μž₯λœλ‹€.
  • ν•˜μ§€λ§Œ κΌ­ λ°±μ—”λ“œμ—μ„œ μ „λ‹¬λ˜λŠ” κ°’ μ›ν˜• κ·ΈλŒ€λ‘œλ₯Ό λ°˜ν™˜ν•  ν•„μš”λŠ” μ—†μœΌλ―€λ‘œ queryFn을 ν†΅ν•΄μ„œ 값을 λ³€ν˜•ν•˜λŠ” 것도 κ°€λŠ₯ν•˜λ‹€.
    - 이λ₯Ό 톡해 ν”„λ‘ νŠΈμ—”λ“œμ—μ„œλŠ” 마치 μ›λž˜ λ°±μ—”λ“œμ—μ„œ 이 ν˜•νƒœμ˜ 값을 전달받은 κ²ƒμ²˜λŸΌ μ‚¬μš©ν•  수 μžˆλ‹€.
const fetchAnimalList = async (): Promise<Animal[]> => {
  const response = await axios.get('/animalList/Seoul')
  const data: Animal[] = response.data

  return data.map((animal) => animal.name.toUpperCase())
}

export const useGetAnimalList = () =>
  useQuery({
    queryKey: ['animalList'],
    queryFn: fetchAnimalList,
  })
  • μœ„μ˜ μ½”λ“œμ˜ 경우, ν”„λ‘ νŠΈμ—”λ“œ μΈ‘μ—μ„œλŠ” λ™λ¬Όμ˜ 이름이 λŒ€λ¬Έμžκ°€ μ•„λ‹Œ 데이터, 즉 원본 λ°μ΄ν„°μ—λŠ” μ ‘κ·Όν•  수 μ—†λ‹€.
    - react-query-devtoolsμ—μ„œλŠ” λ³€ν˜•λœ ꡬ쑰의 λ°μ΄ν„°λ§Œ λ³Ό 수 μžˆλ‹€.
    • λ„€νŠΈμ›Œν¬λ₯Ό 좔적해야 원본 데이터 ꡬ쑰λ₯Ό λ³Ό 수 μžˆλ‹€.
  • react-queryκ°€ μ΅œμ ν™”λ₯Ό 해쀄 수 μ—†λ‹€.
    - 데이터 fetching이 싀행될 λ•Œλ§ˆλ‹€ 데이터 λ³€ν˜•λ„ 같이 λ™μž‘ν•œλ‹€.
    • 이것은 κ³Όλ„ν•œ μ½”μŠ€νŠΈλ‘œ λ‹€λ₯Έ λŒ€μ•ˆμ„ 찾아보아야 ν•  원인이 될 수 μžˆλ‹€.
  • 데이터 fetching을 μΆ”μƒν™”ν•˜μ—¬ APIλ₯Ό κ³΅μœ ν•˜λŠ” 경우 이 μˆ˜μ€€μ˜ 데이터 λ³€ν˜•μ— λŒ€ν•œ μ ‘κ·Ό κΆŒν•œμ΄ 없을 μˆ˜λ„ μžˆλ‹€.

β­• μž₯점

  • λ°±μ—”λ“œμ—μ„œ 데이터λ₯Ό 받은 κ²ƒμ²˜λŸΌ μ‚¬μš©ν•  수 μžˆλ‹€.

❌ 단점

  • λ³€ν˜•λœ 데이터가 cache에 μ €μž₯λ˜μ–΄ 원본 데이터 ꡬ쑰에 μ ‘κ·Όν•  수 μ—†λ‹€.
  • λͺ¨λ“  데이터 fetchingμ—μ„œ μž‘λ™ν•œλ‹€.
  • 자유둭게 λ³€κ²½ν•  수 μ—†λŠ” 곡유 APIλ₯Ό 가진 경우 μ‹€ν˜„ λΆˆκ°€λŠ₯ν•˜λ‹€.

🟑 λ Œλ”λ§ ν•¨μˆ˜μ—μ„œ κ΅¬ν˜„ν•˜κΈ°

  • custom hook을 μ‚¬μš©ν•˜μ—¬ 데이터 λ³€ν˜•μ„ ν•  μˆ˜λ„ μžˆλ‹€.
const fetchAnimalList = async (): Promise<Animal[]> => {
  const response = await axios.get('/animalList/Seoul')
  return response.data
}

export const useGetAnimalList = () => {
  const queryInfo = useQuery({
    queryKey: ['animalList'],
    queryFn: fetchAnimalList,
  })

  return {
    ...queryInfo,
    data: queryInfo.data?.map((animal) => animal.name.toUpperCase()),
  }
}
  • μ΄λ ‡κ²Œ κ΅¬ν˜„ν•  경우 λͺ¨λ“  fetch ν•¨μˆ˜κ°€ λ™μž‘ μ‹œ 뿐만 μ•„λ‹ˆλΌ λͺ¨λ“  λ Œλ”λ§ μ‹œ 이 ν•¨μˆ˜κ°€ μ‹€ν–‰λ˜κ²Œ λœλ‹€.
    - 데이터 fetching을 ν¬ν•¨ν•˜μ§€ μ•ŠλŠ” λ Œλ”λ§μ—μ„œλ„!
πŸ‘©β€πŸ’» useMemoλ₯Ό μ‚¬μš©ν•˜μ—¬ μ΄λŸ¬ν•œ 문제λ₯Ό ν•΄κ²°ν•΄λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

useMemo둜 μ΅œμ ν™”ν•˜κΈ°

  • μ΄λ•Œ μ£Όμ˜ν•  점은, 쒅속성을 μ΅œλŒ€ν•œ 쒁게 μ •μ˜ν•΄μ•Ό ν•œλ‹€λŠ” 것이닀.

    data: React.useMemo(
          () => queryInfo.data?.map((animal) => animal.name.toUpperCase()),
          [queryInfo]
    )
    • 이런 μ‹μœΌλ‘œ μ‚¬μš©ν•  경우 useMemoλ₯Ό μ‚¬μš©ν•˜λŠ” μ˜λ―Έκ°€ μ—†λ‹€.
    • queryInfoλ₯Ό μ˜μ‘΄μ„± 배열에 μΆ”κ°€ν•  경우 κ²°κ΅­ 맀 λ Œλ”λ§λ§ˆλ‹€ λ‹€μ‹œ λ³€ν˜•μ΄ λ™μž‘ν•˜λŠ” 것은 λ™μΌν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
  • μ•„λž˜μ™€ 같은 λ°©μ‹μœΌλ‘œ κ΅¬ν˜„ν•΄λ³΄μž.

export const useGetAnimalList = () => {
  const queryInfo = useQuery({
    queryKey: ['animalList'],
    queryFn: fetchAnimalList,
  })

  return {
    ...queryInfo,
    data: React.useMemo(
      () => queryInfo.data?.map((animal) => animal.name.toUpperCase()),
      [queryInfo.data]
    ),
  }
}
  • queryInfo λ‚΄λΆ€μ˜ dataλŠ” 무언가가 μ‹€μ œλ‘œ λ³€ν•˜λŠ” 경우, λ‹€μ‹œ 말해 λ‹€μ‹œ λ°μ΄ν„°μ˜ λ³€ν˜•μ„ μˆ˜ν–‰ν•˜κ³  싢은 상황이 μ•„λ‹ˆλΌλ©΄ 참쑰적으둜 μ•ˆμ „ν•˜λ‹€.

  • 여기에 데이터 λ³€ν˜•κ³Ό ν•¨κ»˜ μΆ”κ°€λ‘œ μ‚¬μš©ν•˜κ³  싢은 λ‘œμ§μ„ 넣어주어도 μ’‹λ‹€.
    - ν•˜μ§€λ§Œ 데이터가 undefined 될 수 μžˆμœΌλ―€λ‘œ μ˜΅μ…”λ„ 체이닝을 κΌ­ 같이 μ‚¬μš©ν•΄μ£Όλ„λ‘ ν•˜μž.

  • μΆ”κ°€λ‘œ, React Queryκ°€ v4λΆ€ν„° Tracked Queries μΆ”κ°€ν•΄μ„œ μœ„μ˜ ...queryInfo와 같은 ꡬ쑰뢄해할당 μ½”λ“œλŠ” μ§€μ–‘ν•˜λŠ” μΆ”μ„Έκ°€ λ˜μ—ˆμŒμ— μ£Όμ˜ν•˜μž.

β­• μž₯점

  • useMemoλ₯Ό μ‚¬μš©ν•˜μ—¬ μ΅œμ ν™”ν•  수 μžˆλ‹€.

❌ 단점

  • κ°œλ°œμžλ„κ΅¬μ—μ„œ μ •ν™•ν•œ ν˜•νƒœλ₯Ό 검사할 수 μ—†λ‹€.
  • ꡬ쑰가 쑰금 더 λ³΅μž‘ν•΄μ§„λ‹€.
  • 데이터가 undefined 될 수 μžˆλ‹€.
  • tracked queries와 ν•¨κ»˜ μ‚¬μš©ν•˜λŠ” 것은 ꢌμž₯λ˜μ§€ μ•ŠλŠ”λ‹€.

🀠 Select μ˜΅μ…˜μ„ μ‚¬μš©ν•΄λ΄…μ‹œλ‹€!

πŸ‘Ύ Select μ˜΅μ…˜

  • select: (data: TData) => unknown

  • optionalν•œ 속성이닀.

  • query ν•¨μˆ˜λ‘œ λ°˜ν™˜λ˜λŠ” λ°μ΄ν„°μ˜ 일뢀λ₯Ό λ³€ν˜•ν•˜κ±°λ‚˜ 선택할 수 μžˆλ‹€.

  • λ°˜ν™˜λ˜λŠ” λ°μ΄ν„°μ˜ 값에 영ν–₯을 λ―ΈμΉ˜μ§€λ§Œ 쿼리 cache에 μ €μž₯λ˜λŠ” κ°’μ—λŠ” 영ν–₯을 λ―ΈμΉ˜μ§€ μ•ŠλŠ”λ‹€.

  • 데이터가 μ‘΄μž¬ν•  λ•Œμ—λ§Œ selectorκ°€ ν˜ΈμΆœλ˜λ―€λ‘œ undefined 값에 λŒ€ν•΄ κ±±μ •ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.

  • 예λ₯Ό λ“€μ–΄, 동물 ꡬ뢄이 '개' 에 ν•΄λ‹Ήν•˜λŠ” 동물을 ν•„ν„°λ§ν•œλ‹€κ³  κ°€μ •ν•΄λ³΄μž.

const useGetAnimalList = (queryKeys, func, filterLogic) => useQuery(
	[queryKeys],
    () => func(),
    {
    	select: (animals) => animals.filter(filterLogic)
    }
)
  • filterλ‘œμ§μ—λŠ” (animal) => animal.category === '개'κ°€ λ“€μ–΄κ°ˆ 수 μžˆμ„ 것이고, category와 '개' λΆ€λΆ„λ§Œ μž¬μ‚¬μš©μ΄ κ°€λŠ₯ν•˜λ„λ‘ λ‹€λ₯Έ κ°’μœΌλ‘œ λ°”κΎΌλ‹€λ©΄ νŽΈλ¦¬ν•˜κ²Œ useQuery만 μ‚¬μš©ν•΄μ„œ 필터링 λ‘œμ§μ„ κ΅¬ν˜„ν•  수 μžˆλ‹€.
  • useQuery의 cache 된 데이터λ₯Ό κ±΄λ“œλ¦¬μ§€ μ•ŠμœΌλ©΄μ„œ 필터링을 κ΅¬ν˜„ν•˜λ―€λ‘œ useQuery의 normalization(μ •κ·œν™”, 데이터 쀑볡 방지)에 λŒ€ν•œ μž₯점과 필터링 λ‘œμ§μ„ λ™μ‹œμ— μ‚¬μš©ν•  수 있게 λœλ‹€.

πŸ’Ύ Selector Memoization

  • selector ν•¨μˆ˜λ₯Ό μœ„μ˜ μ½”λ“œμ™€ 같이 인라인 ν•¨μˆ˜λ‘œ μ •μ˜ν•  경우 맀 λ Œλ”λ§λ§ˆλ‹€ ν•΄λ‹Ή ν•¨μˆ˜κ°€ λ™μž‘ν•˜κ²Œ λœλ‹€.
    • μ΄λŠ” κ³Όλ„ν•œ μ½”μŠ€νŠΈλ₯Ό λ°œμƒμ‹œν‚¬ 수 μžˆμœΌλ―€λ‘œ ν•¨μˆ˜λ₯Ό memoizeν•˜μž!

useCallback μ‚¬μš©ν•˜κΈ°

select: React.useCallback((animals) => animals.filter(filterLogic), [])

ν•¨μˆ˜μ˜ μ°Έμ‘°λ₯Ό μ•ˆμ •μ μœΌλ‘œ λ§Œλ“€κΈ°

const transFormLogic = (data: Animal[], filterLogic) =>
  data.map((animals) => animals.filter(filterLogic))
  
const useGetAnimalList = (queryKeys, func, filterLogic) => useQuery(
	[queryKeys],
    () => func(),
    {
    	select: transFormLogic
    }
)

🧐 selectν•΄μ•Ό ν•˜λŠ” 값이 μ—¬λŸ¬ μ’…λ₯˜λΌλ©΄?

  • select μ˜΅μ…˜μ€ λ°μ΄ν„°μ˜ μΌλΆ€λ§Œμ„ κ΅¬λ…ν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•  μˆ˜λ„ μžˆλ‹€.
export const useGetAnimalList = (select) =>
  useQuery({
    queryKey: ['animalList'],
    queryFn: fetchAnimalList,
    select,
  })

export const useOnlyCategory = (targetCategory: string) =>
  useGetAnimalList((data) => data.map((animal) => animal.category))
  
export const useFindAnimal = (targetName: string) =>
  useGetAnimalList((data) => data.find((animal) => animal.name === targetName))
  • μ΄λ ‡κ²Œ useGetAnimalList에 μ»€μŠ€ν…€ selector듀을 λ„˜κ²¨μ€ŒμœΌλ‘œμ¨ 원본 λ°μ΄ν„°μ—μ„œ ν•„μš”ν•œ λ°μ΄ν„°λ§Œ λ°›μ•„ μ“Έ 수 μžˆλ‹€.
    - μ΄λ•Œ select ν•¨μˆ˜λ₯Ό 인자둜 λ„˜κ²¨μ£Όμ§€ μ•Šμ„ 경우 cache 된 전체 데이터가 λ°˜ν™˜λœλ‹€.
    • ν•˜μ§€λ§Œ select ν•¨μˆ˜λ₯Ό λ„˜κ²¨μ£Όλ©΄ ν•΄λ‹Ή λ°μ΄ν„°λ§Œ 확인할 수 있게 λœλ‹€.
  • λ§Œμ•½ λ°μ΄ν„°μ—μ„œ λ™λ¬Όλ“€μ˜ 이름이 λ°”λ€Œμ—ˆλ”λΌλ„ useOnlyCategoryλ₯Ό 톡해 μΉ΄ν…Œκ³ λ¦¬ μ •λ³΄λ§Œ κ΅¬λ…ν•˜κ³  μžˆλŠ” μ»΄ν¬λ„ŒνŠΈλŠ” λ¦¬λ Œλ”λ§λ˜μ§€ μ•ŠλŠ”λ‹€.
    - λ¬Όλ‘  React Query의 메타 데이터 쀑 isFetching의 μƒνƒœ 변화에 따라 두 번 λ Œλ”λ§ 될 κ°€λŠ₯성은 μžˆλ‹€.

β­• μž₯점

  • μ΅œμ ν™”μ— κ°€μž₯ μ ν•©ν•˜λ‹€.
  • 뢀뢄적인 μ •λ³΄μ˜ ꡬ독이 κ°€λŠ₯ν•˜λ‹€.

❌ 단점

  • 데이터λ₯Ό κ΄€μ°°ν•˜λŠ” λͺ¨λ“  λΆ€λΆ„μ—μ„œ ꡬ쑰가 λ‹€λ₯Ό 수 μžˆλ‹€.
πŸ‘©β€πŸ’» 이처럼 μ—¬λŸ¬ 방식듀을 μ°Ύμ•„λ³΄λ©΄μ„œ select μ˜΅μ…˜μ΄ μ›ν•˜λŠ” 둜직의 κ΅¬ν˜„μ— κ°€μž₯ μ ν•©ν•˜λ‹€λŠ” 결둠을 λ‚΄λ Έλ‹€! 

πŸ”Ž References

profile
An investment in knowledge pays the best interestπŸ™ƒ

0개의 λŒ“κΈ€