React Query μžμ„Ένžˆ μ•Œμ•„λ³΄κΈ°

React QueryλŠ” 크게 2κ°€μ§€λ‘œ λ‚˜λ‰©λ‹ˆλ‹€.

  • Query : μ„œλ²„ 데이터λ₯Ό κ°€μ Έμ˜€λŠ” 것
  • Mutation : μ„œλ²„ λ°μ΄ν„°μ˜ 값을 λ°”κΎΈκ±°λ‚˜ μΆ”κ°€ λ˜λŠ” μ‚­μ œν•˜λŠ”κ²ƒ

Query

πŸ’¬ useQuery κΈ°λ³Έ μ„€λͺ…

GET μš”μ²­κ³Ό λΉ„μŠ·ν•˜κ²Œ μ„œλ²„ 데이터λ₯Ό κ°€μ Έμ˜€κΈ° μœ„ν•΄μ„  useQueryλΌλŠ” hook이 μ‚¬μš©λ©λ‹ˆλ‹€. 이 useQueryλ₯Ό μ‚¬μš©ν•  λ•Œ 두가지λ₯Ό μ£Όμ˜ν•΄μ•Ό ν•©λ‹ˆλ‹€.

const data = useQuery(queryKey, queryFn)
  • queryKey : useQueryλ§ˆλ‹€ λ°°μ—΄ ν˜•νƒœλ‘œ λΆ€μ—¬λ˜λŠ” κ³ μœ ν•œ key κ°’μž…λ‹ˆλ‹€. 이 queryKeyλ₯Ό ν†΅ν•΄μ„œ λ‹€λ₯Έ mutation functionμ΄λ‚˜ λ‹€λ₯Έ κ³³μ—μ„œλ„ ν•΄λ‹Ή 쿼리의 κ²°κ³Όλ₯Ό κΊΌλ‚΄μ˜€λŠ”κ²ƒμ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€. React queryλŠ” 이 queryKey둜 캐싱 관리λ₯Ό ν•˜μ—¬ λ°μ΄ν„°λ§ˆλ‹€ κ³ μœ ν•œ key 값을 μ§€μ •ν•˜λŠ”κ²Œ μ€‘μš”ν•©λ‹ˆλ‹€. 더 λ‚˜μ•„κ°€μ„œ νŠΉμ •ν•œ 데이터λ₯Ό κ°€μ Έμ˜€κΈ° μœ„ν•΄μ„œ λ°μ΄ν„°μ˜ μ•„μ΄λ””λ‚˜ ν•„μš”ν•œ 필터링 쑰건을 λ°°μ—΄ ν˜•νƒœλ‘œ λ³΄λ‚΄μ€„μˆ˜ μžˆμŠ΅λ‹ˆλ‹€.
/posts => ["posts"]
/posts/1 => ["posts", post.id]
/posts?authorId=1 => ["posts", {authorId : 1}]
/posts/2/comments => ["posts", post.id, "comments"]
  • queryFn : Promise μ²˜λ¦¬κ°€ μ΄λ€„μ§€λŠ” ν•¨μˆ˜λ‘œ μ„œλ²„μ— apiλ₯Ό μš”μ²­ν•˜λŠ” μ½”λ“œμž…λ‹ˆλ‹€.

useQueryλŠ” λΉ„λ™κΈ°λ‘œ μž‘λ™ν•©λ‹ˆλ‹€. λ§Œμ•½ μ—¬λŸ¬κ°œμ˜ useQueryκ°€ ν•œ μ»΄ν¬λ„ŒνŠΈμ—μ„œ μ‚¬μš©λ  경우 ν•˜λ‚˜κ°€ λλ‚œλ‹€μŒ λ‹€μŒ useQueryκ°€ μ‹€ν–‰λ˜λŠ” 것이 μ•„λ‹Œ μ—¬λŸ¬κ°œμ˜ useQueryκ°€ λ™μ‹œμ— μ‹€ν–‰λ©λ‹ˆλ‹€.


πŸ“Œ useQuery return κ°’

  const queryClient = useQueryClient();
  const { data, isLoading, isError, error, isSuccess, status} = useQuery({
    queryKey: ["posts"],
    queryFn: () => wait(1000).then(() => [...POSTS]),
  });

  if (isLoading || status === 'loading') return <div>...isLoading</div>;
  if (isError || status === 'error') {
    return <pre>{JSON.stringify(error)}</pre>;
  }

  return (
    <div>{data.map((data) => (<p key={data.id}>{data.title}</p>))}</div>
  )
  • data : queryFn ν•¨μˆ˜λ₯Ό λžœλ”ν•œ 결과값을 λ‚˜νƒ€λƒ…λ‹ˆλ‹€.
  • isLoading : boolean ν˜•νƒœμ˜ κ°’μœΌλ‘œ μ„œλ²„μ—μ„œ 데이터λ₯Ό λ°›μ•„μ˜€λŠ” λ™μ•ˆ λ‘œλ”© 화면을 보여주고 μ‹Άλ‹€λ©΄ 이 값이 μœ μš©ν•˜κ²Œ μ“°μΌκ²ƒμž…λ‹ˆλ‹€.
  • isError : boolean ν˜•νƒœμ˜ κ°’μœΌλ‘œ μ—¬λŸ¬λ²ˆμ˜ μ‹œλ„λμ— 계속 queryFn ν•¨μˆ˜κ°€ μ—λŸ¬λ₯Ό λ‚Έλ‹€λ©΄ 이 κ°’μœΌλ‘œ μ—λŸ¬λ₯Ό ν™•μ‹ ν• μˆ˜ μžˆμŠ΅λ‹ˆλ‹€.
  • error : μ—λŸ¬κ°€ μƒκ²Όμ„λ•Œ 화면에 μ—λŸ¬ λ©”μ„Έμ§€λ₯Ό 보이게 ν•˜κ³  μ‹Άκ±°λ‚˜ ν™•μΈν•˜κ³  μ‹Άλ‹€λ©΄ 이 값을 μ“Έμˆ˜ μžˆμŠ΅λ‹ˆλ‹€.
  • isSuccess : boolean ν˜•νƒœμ˜ κ°’μœΌλ‘œ μ„±κ³΅μ μœΌλ‘œ 데이터λ₯Ό λ°›μ•„μ™”λŠ”μ§€λ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.
  • status : error, loading, success쀑 κ°’μ˜ μƒνƒœλ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.
  • fetchStatus :
    • fetching : ν˜„μž¬ 데이터λ₯Ό κ°€μ§€κ³  μ˜€λŠ” μ€‘μΌλ•Œλ₯Ό λœ»ν•©λ‹ˆλ‹€
    • idle : ν˜„μž¬ 아무것도 ν•˜μ§€ μ•Šκ³  μžˆκ±°λ‚˜ 데이터λ₯Ό κ°€μ Έμ˜¨ ν›„λ₯Ό λœ»ν•©λ‹ˆλ‹€.
    • paused : 데이터λ₯Ό κ°€μ Έμ˜€λŠ”μ€‘μ— 인터넷과 λŠκ²Όκ±°λ‚˜ μ–΄λ– ν•œ 이유둜 λ©ˆμ·„μ„ λ•Œλ₯Ό λœ»ν•©λ‹ˆλ‹€.

πŸ“Œ useQueryλ₯Ό μ‚¬μš©ν•΄ fetching ν• λ•Œμ˜ κ³Όμ •

  1. νŽ˜μ΄μ§€μ•ˆμ˜ μ»΄ν¬λ„ŒνŠΈμ— useQueryκ°€ μžˆμ„λ•Œ useQueryλŠ” μ‹€ν–‰λ˜λ©° λ°‘μ˜ 상황듀이 μΌμ–΄λ‚ λ•Œ λ‹€μ‹œ μ‹€ν–‰λ©λ‹ˆλ‹€.

    • νŽ˜μ΄μ§€ μƒˆλ‘œκ³ μΉ¨
    • ν•œ νŽ˜μ΄μ§€μ—μ„œ λ‹€λ₯Έ νŽ˜μ΄μ§€λ‘œ λ„˜μ–΄κ°ˆλ•Œ
    • 마우슀 포컀슀λ₯Ό λ‹€λ₯Έ 곳에 λ’€λ‹€κ°€ λ‹€μ‹œ νŽ˜μ΄μ§€λ‘œ λŒμ•„κ°ˆλ•Œ
    • 인터넷이 λŠκ²Όλ‹€κ°€ λ‹€μ‹œ λŒμ•„μ˜€λŠ” λ™μ‹œμ— νŽ˜μ΄μ§€λ₯Ό λ³΄μ—¬μ€„λ•Œ
  2. QueryKeyλ₯Ό μ‚¬μš©ν•΄ 데이터가 stale인지λ₯Ό μ•Œμ•„λ³Έλ‹€μŒ refetchλ₯Ό ν•©λ‹ˆλ‹€.

  3. refetch된 데이터λ₯Ό 화면에 λ³΄μ—¬μ€λ‹ˆλ‹€.

제일 μ²˜μŒμ— νŽ˜μ΄μ§€λ₯Ό λ‘œλ”©ν• λ•Œ

fetchStatus : fetching
 status : loading

데이터λ₯Ό μ„±κ³΅μ μœΌλ‘œ 가져왔을 λ•Œ

fetchStatus : idle
 status : success (μ‹€νŒ¨ν•  경우 "error")

λ‹€λ₯Έ νŽ˜μ΄μ§€λ‘œ λ„˜μ–΄κ°ˆλ•Œ λ˜‘κ°™μ€ 데이터λ₯Ό λ„˜μ–΄κ°€λŠ” νŽ˜μ΄μ§€μ—μ„œλ„ λΆ€λ₯Ό 경우 (Refetching)

fetchStatus : fetching
 status : success (μ‹€νŒ¨ν•  경우 "error")

πŸ“Œ useQueryμ—μ„œ 자주 μ‚¬μš©λ˜λŠ” μ˜΅μ…˜

  const { data, isLoading, isError, error, isSuccess, status} = useQuery({
    queryKey: ["posts", postQuery?.data?.postId],
    queryFn: getPosts,
    enabled: postQuery?.data.postId !== null,
    staleTime : 1000,
    refetchInterval : 1000,
  });

enabled : μœ„μ—μ„œ λ§ν–ˆλ“―μ΄ useQueryλŠ” λΉ„λ™κΈ°λ‘œ μž‘λ™ν•©λ‹ˆλ‹€. λ§Œμ•½ μ—¬λŸ¬κ°œμ˜ useQueryλ₯Ό ν•œ μ»΄ν¬λ„ŒνŠΈμ—μ„œ λ™κΈ°λ‘œ μž‘λ™ν•˜κ³  μ‹Άλ‹€λ©΄ enableλ₯Ό μ‚¬μš©ν• μˆ˜ μžˆμŠ΅λ‹ˆλ‹€. boolean ν˜•νƒœμ˜ 쑰건을 λ„£μ–΄μ£Όλ©΄μ„œ 쑰건이 trueμΌλ•Œ useQueryλ₯Ό μž‘λ™μ‹œν‚΅λ‹ˆλ‹€.

staleTime : 데이터λ₯Ό freshμƒνƒœμ—μ„œ staleμƒνƒœλ‘œ μ „ν™˜λ  λ•ŒκΉŒμ§€μ˜ μ‹œκ°„μ„ 정해쀄 수 μžˆμŠ΅λ‹ˆλ‹€. λ§Œμ•½ 5뢄을 μ§€μ •ν•΄μ€€λ‹€λ©΄ 5λΆ„λ™μ•ˆμ€ 데이터가 freshμƒνƒœλ₯Ό μœ μ§€ν• κ²ƒμ΄λ©° freshμƒνƒœμ˜ λ°μ΄ν„°λŠ” 항상 cacheμ—μ„œ μ½μ–΄μ˜¨λ‹€λŠ” 것을 λœ»ν•©λ‹ˆλ‹€.

refetchInterval: milisecond λ‹¨μœ„μ˜ μ‹œκ°„μœΌλ‘œ μ§€μ •ν•œ μ‹œκ°„λ§ˆλ‹€ 데이터λ₯Ό refetch ν• μˆ˜ μžˆμŠ΅λ‹ˆλ‹€. λ§Œμ•½ 1000λ₯Ό μ§€μ •ν•΄μ£Όμ—ˆλ‹€λ©΄ 1μ΄ˆλ§ˆλ‹€ ν•œλ²ˆμ”© 데이터λ₯Ό refetch ν•΄μ€λ‹ˆλ‹€.

πŸ’‘ staleμ΄λž€? React queryλŠ” μΊμ‹±λœ dataλ₯Ό staleν•œ μƒνƒœλ‘œ μ—¬κΉλ‹ˆλ‹€. μ—¬κΈ°μ„œ stale은 "μ‹ μ„ ν•˜μ§€ μ•Šλ‹€"λΌλŠ” 뜻이 μžˆλŠ”λ°μš”. 이 staleν•œ dataλŠ” μ΅œμ‹ ν™”κ°€ ν•„μš”ν•œ λ°μ΄ν„°λΌλŠ” 의미둜 μ»΄ν¬λ„ŒνŠΈκ°€ λ§ˆμš΄νŠΈλ‚˜ μ—…λ°μ΄νŠΈλ˜λ©΄ refetchκ°€ λ©λ‹ˆλ‹€.


Mutation

πŸ’¬ useMutation κΈ°λ³Έ μ„€λͺ…

POSTλ‚˜ PUT μš”μ²­κ³Ό λΉ„μŠ·ν•˜κ²Œ μ„œλ²„ λ°μ΄ν„°μ˜ 값을 λ°”κΎΈκ±°λ‚˜ μΆ”κ°€ λ˜λŠ” μ‚­μ œν•˜κΈ° μœ„ν•΄μ„  useMutationμ΄λΌλŠ” hook이 μ‚¬μš©λ©λ‹ˆλ‹€. λ°›λŠ” props듀이 μœ„μ˜ useQuery와 λ™μΌν•˜μ§€λ§Œ κ·Έ 외에 또 λ‹€λ₯Έ ν•˜λ‚˜μ˜ mutate λ˜λŠ” mutateAsyncμ΄λΌλŠ” propsλ₯Ό λ°›μŠ΅λ‹ˆλ‹€. mutate λ˜λŠ” mutateAsyncλŠ” ν•¨μˆ˜ ν˜•νƒœμ΄λ©° useMutation을 μ‹€ν–‰ν• μˆ˜ 있게 λ„μ™€μ€λ‹ˆλ‹€.

  • mutationFn : queryFnκ³Ό 같이 Promise μ²˜λ¦¬κ°€ μ΄λ€„μ§€λŠ” ν•¨μˆ˜λ‘œ μ„œλ²„μ— apiλ₯Ό μš”μ²­ν•˜λŠ” μ½”λ“œμž…λ‹ˆλ‹€. ν•˜λ‚˜μ˜ propsλ₯Ό λ°›μ„μˆ˜ 있으며 이 값을 ν•¨μˆ˜μ— μ „λ‹¬ν•˜μ—¬ μ‚¬μš©ν• μˆ˜ μžˆμŠ΅λ‹ˆλ‹€.

  • onSuccess : μ˜ˆμƒλŒ€λ‘œ μž‘λ™ν–ˆμ„λ•Œ μ‹€ν–‰λ˜λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€. onSuccessλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄ mutationFn이 λžœλ”λœ 후에도 화면에 바뀐 κ²°κ³Όκ°€ λ‚˜νƒ€λ‚˜μ§€ μ•Šμ„κ²λ‹ˆλ‹€. κ·Έ μ΄μœ λ‘œλŠ” mutation은 λ‹¨μˆœνžˆ uniquekeyname을 κ°€μ§€κ³  μžˆλŠ” 값을 바꾸어버릴 뿐 λ‹€μ‹œ fetch ν•΄μ˜€μ§€ μ•ŠκΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€. onSuccessκ°€ μ‹€ν–‰λ λ•Œ μ„Έκ°œμ˜propsλ₯Ό λ°›λŠ”λ°μš”. dataλŠ” 결과값을 λ‚˜νƒ€λ‚΄κ³  propsλŠ” mutationFnμ—μ„œ μ „λ‹¬ν–ˆλ˜ κ°’κ³Ό 같은 값을 λ°›μŠ΅λ‹ˆλ‹€. λ§ˆμ§€λ§‰μœΌλ‘œ contextλŠ” onMutate ν•¨μˆ˜μ—μ„œ return ν•˜λŠ” 값을 λœ»ν•©λ‹ˆλ‹€.

    • queryClient.invalidateQueries : queryKeyλ₯Ό μ‚¬μš©ν•΄ 값을 λ‹€μ‹œ refetchλ₯Ό ν• μˆ˜ μžˆμŠ΅λ‹ˆλ‹€.
  • onError : μ˜ˆμƒλŒ€λ‘œ μž‘λ™ν•˜μ§€ μ•Šμ•˜μ„λ•Œ μ‹€ν–‰λ˜λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€.

  • onSettled : μœ„μ˜ onErrorκ³Ό onSuccess와 λ‹€λ₯΄κ²Œ 4개의 propsλ₯Ό λ°›μ•„λ‚΄λ©° data와 error을 λ‹€ 전달 λ°›μ„μˆ˜ μžˆμŠ΅λ‹ˆλ‹€.

  • onMutate : μœ„μ˜ μ„Έκ°œμ˜ ν•¨μˆ˜μ™€ λ‹€λ₯΄κ²Œ propsλ₯Ό ν•˜λ‚˜λ§Œ 전달 λ°›λŠ” ν•¨μˆ˜ μž…λ‹ˆλ‹€. 이 ν•¨μˆ˜λŠ” mutationFn이 μž‘λ™λ˜κΈ° 전에 μ‹€ν–‰λ˜λ©° μ—¬κΈ°μ„œ λ³΄λ‚΄λŠ” 값은 onSuccess의 context둜 μ „λ‹¬λ©λ‹ˆλ‹€. 데이터λ₯Ό μ—¬λŸ¬λ²ˆ μƒμ„±λ˜λŠ” λ³€κ²½λ˜λŠ”κ±Έ λ§‰κΈ°μœ„ν•΄ 이 ν•¨μˆ˜λŠ” μ˜ˆμƒλŒ€λ‘œ μž‘λ™ν•˜μ§€ μ•Šμ•„λ„ λ‹€μ‹œ μ‹œλ„λ₯Ό 해보렀 ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ retryλ₯Ό 톡해 λ‹€μ‹œ μ‹œλ„ ν•΄λ³Ό 수λ₯Ό μ§€μ •ν• μˆ˜ μžˆμŠ΅λ‹ˆλ‹€.

  const queryClient = useQueryClient();

  const {data, error, status,isIdle, isSuccess, isError, mutate } =
  useMutation({
    mutationFn: (props) => wait(1000).then(() => POSTS.push({ id: 1, props })),
    onSuccess: (data, props, context) => {
      queryClient.invalidateQueries(["posts"]);
    },
    onError: (error, props, context) => {
      ...
    },
    onSettled: (data, error, props, context) => {
      ...
    },
    retry: 3,
    onMutate: props => {
      return {key:"value"}
    },
  });


  return (
      <button onClick={()=> mutate('New post') } />
      <button onClick={()=> mutateAsync('New post').then(()=>{...}) } />
  )

Devtool

React Query DevtoolsλŠ” React Query의 μž₯μ μ€‘μ˜ ν•˜λ‚˜μΈ κ°•λ ₯ν•œ λ‚΄μž₯ 개발 λ„κ΅¬μž…λ‹ˆλ‹€. μ‚¬μš©μ€‘μΈ λͺ¨λ“  쿼리 μƒνƒœλ“€μ„ μ‹œκ°ν™”ν•˜μ—¬ ν™•μΈν• μˆ˜ 있게 도와주고 μ—λŸ¬κ°€ μƒκΈ°κ±°λ‚˜ μ˜ˆμƒλŒ€λ‘œ μž‘λ™ν•˜μ§€ μ•ŠλŠ” 경우 문제λ₯Ό ν•΄κ²°ν• μˆ˜ 있게 λ„μ™€μ€λ‹ˆλ‹€.

πŸ“Œ μ„€μΉ˜λ°©λ²•

npm i @tanstack/react-query-devtools
yarn add @tanstack/react-query-devtools

πŸ“Œ μ‚¬μš©λ°©λ²•

react-queryκ°€ μ œκ³΅ν•˜λŠ” QueryClientProvider 사이에 ReactQueryDevtoolsλ₯Ό λ„£μ–΄μ€˜μ•Ό ν•©λ‹ˆλ‹€. μ—¬κΈ°μ„œ props둜 clientλ₯Ό μ „λ‹¬ν•΄μ€˜μ•Ό ν•˜λŠ”λ° QueryClient의 μΈμŠ€ν„΄μŠ€λ₯Ό μ „λ‹¬ν•΄μ£ΌλŠ”κ²ƒμœΌλ‘œ λ§Œμ‘±μ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { QueryClient, QueryClientProvider } from 'react-query';


const queryClient = new QueryClient();

...

return
  <QueryClientProvider client={queryClient}>
    <ReactQueryDevtools initialIsOpen={false} position='bottom-right' />
  </QueryClientProvider>

// initialIsOpen : open된 μ±„λ‘œ μ‹œμž‘
// position : devtoolsλ₯Ό μ—΄ 수 μžˆλŠ” logo μœ„μΉ˜ - 우츑 ν•˜λ‹¨μœΌλ‘œ μ§€μ •