๐Ÿ€ React-Query ์‚ฌ์šฉํ•ด๋ณด๊ธฐ (1) useMutation

ํ•ด๋กฑ๊ทธยท2024๋…„ 4์›” 21์ผ

react

๋ชฉ๋ก ๋ณด๊ธฐ
12/14

React-Query

React-Query๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ์ƒˆ๋กœ์šด ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜์„ ์ œ๊ณตํ•œ๋‹ค.

useQuery

useQuery๋ฅผ ์ด์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ fetching ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ HTTP GET ์š”์ฒญ์„ ํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
useQuery๋Š” queryKey, queryFn, options๋ฅผ ์ด์šฉํ•ด์„œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

  • queryKey: Query๋ฅผ ์œ ๋‹ˆํฌํ•˜๊ฒŒ ์ง€์นญํ•  ๋ฐฐ์—ด
  • queryFn: Promise๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ํ•จ์ˆ˜
  • options: ๋‹ค์–‘ํ•œ ์˜ต์…˜๋“ค์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด

useMutation

React-Query๋ฅผ ์ด์šฉํ•ด ์„œ๋ฒ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋ฉฐ HTTP POST, PUT, DELETE ์š”์ฒญ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. (C,U,D)

์‚ฌ์šฉ๋ฒ•

mutationFn

  • mutationFn์€ queryFn์ฒ˜๋Ÿผ Promise๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜
  • axios๋ฅผ ์ด์šฉํ•˜์—ฌ ์„œ๋ฒ„์— API๋ฅผ ์š”์ฒญํ•˜๋Š” ๋ถ€๋ถ„

mutationFn ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” useMutation์˜ ๋ฆฌํ„ด๊ฐ’์ธ mutate ํ•จ์ˆ˜์˜ ์ธ์ž๋กœ ์ „๋‹ฌ๋œ๋‹ค. mutationFn์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ• ์ง€ ์ •์˜๋งŒ ํ•˜๊ณ , mutate ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋‚˜์ค‘์— ์‹คํ–‰๋˜๋Š” ๊ฒƒ์ด๋‹ค.

// ๋ฐฉ๋ฒ•1
const DeleteData = useMutation(() => axios.delete(`/api/delete/${fundId}`));

// ๋ฐฉ๋ฒ•2
const DeleteData = useMutation({
  mutationFn: (fundId) => axios.delete(`/api/delete/${fundId}`)
})

mutate

  • useMutation์„ ์ด์šฉํ•ด ์ž‘์„ฑํ•œ ๋‚ด์šฉ๋“ค์ด ์‹ค์ œ๋กœ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ํŠธ๋ฆฌ๊ฑฐ ์—ญํ• ์ž
  • useMutation์„ ์ •์˜ํ•ด์ค€ ํ›„, ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜
const { mutate } = () => deleteData();

const deleteFn = () => {
  deleteData.mutate(fundId)
}

onSuccess, onError, onSettled

  • async/await ๋งŒ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ
const deleteData = async () => {
  try {
    const response = await axios.delete(`/api/delete/${fundId}`)
    	.then((response) => console.log('๐Ÿ‘Œ๐ŸปํŽ€๋”ฉ ์ƒ์„ฑ ์„ฑ๊ณต', response.data))
  }
  catch (error) {
    console.error("โŒํŽ€๋”ฉ ์ƒ์„ฑ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ..", error);
  }
  finally {
    console.log("๊ฒฐ๊ณผ๋ž‘ ์ƒ๊ด€์—†์ด ๋ญ”๊ฐ€ ์‹คํ–‰๋จ")
  }
}
  • useMutation ์ ์šฉํ•œ ๊ฒฝ์šฐ
// fn key๊ฐ’ ์ƒ๋žตver
const deleteData = useMutation((fundId) => axios.delete(`/api/delete/${fundId}`), {
  onSuccess: () => { console.log('๐Ÿ‘Œ๐ŸปํŽ€๋”ฉ ์ƒ์„ฑ ์„ฑ๊ณต') },
  onError: () => { console.error('โŒํŽ€๋”ฉ ์ƒ์„ฑ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ') },
  onSettled: () => { console.log('๊ฒฐ๊ณผ๋ž‘ ์ƒ๊ด€์—†์ด ๋ญ”๊ฐ€ ์‹คํ–‰๋จ') }
})

// fn key๊ฐ’ ๋ช…์‹œver
const deleteData = useMutation({
  mutationFn: () => axios.delete(`/api/delete/${fundId}`),
  onSuccess: () => { console.log('๐Ÿ‘Œ๐ŸปํŽ€๋”ฉ ์ƒ์„ฑ ์„ฑ๊ณต') },
  onError: () => { console.error('โŒํŽ€๋”ฉ ์ƒ์„ฑ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ') },
  onSettled: () => { console.log('๊ฒฐ๊ณผ๋ž‘ ์ƒ๊ด€์—†์ด ๋ญ”๊ฐ€ ์‹คํ–‰๋จ') }
})

onSuccess, onError, onSettled๋Š” useMutation ์ •์˜ํ•  ๋•Œ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, mutate์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

๐ŸŽ€ useMutation์œผ๋กœ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธํ•˜๊ธฐ

  • React-Query๋Š” ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์›ํ•˜๋ฉฐ, ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ๊ฐ€ ์„ฑ๊ณตํ•˜๊ธฐ ์ „์— UI๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ์Œ
  • ์„œ๋ฒ„์™€์˜ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”๋ฅผ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š๊ณ  ๋จผ์ € ์‚ฌ์šฉ์ž์—๊ฒŒ ์„ฑ๊ณต ์‹œ UI๋ฅผ ๋ณด์—ฌ์ค€ ํ›„, ์‘๋‹ต์ด ์˜ค๋ฉด ์„ฑ๊ณต/์‹คํŒจ ์—ฌ๋ถ€์— ๋”ฐ๋ผ UI ์—…๋ฐ์ดํŠธ!
  • ์‚ฌ์šฉ์ž๋Š” ์„œ๋ฒ„์™€์˜ ํ†ต์‹  ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด UI๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ
  • ex) instagram์˜ ์ข‹์•„์š”, kakaotalk์—์„œ ์šฐ์„  ๋ฉ”์„ธ์ง€๊ฐ€ ์ „์†ก๋œ ํ›„(๋งํ’์„  ๋งŒ๋“ค์–ด์ง), ์ทจ์†Œ/์žฌ์ „์†ก ์ฐฝ์ด ๋‚˜์ค‘์— ๋œจ๋Š” ๊ฒƒ ..

onMutate

  • React-Query์—์„œ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•
  • API call ์ „์— ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜
  • ์„ฑ๊ณต ์‹œ ํ˜„์žฌ ๋ฐ์ดํ„ฐ ์บ์‹œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜ UI๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ , ์‹คํŒจ ์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” rollback ๋งค์ปค๋‹ˆ์ฆ˜๋„ ์ œ๊ณตํ•จ
  • onMutate callback ํ•จ์ˆ˜์—์„œ UI๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ , setQueryData ํ•จ์ˆ˜๋กœ ์ด์ „ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ
  • onError ์ฝœ๋ฐฑํ•จ์ˆ˜์—์„œ ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ, ์ด์ „ ๋ฐ์ดํ„ฐ๋กœ rollback
const queryClient = useQueryClient((() => axios.post(`api/like/${id}`), {
  onMutate: async (id) => {
    // 'queryKey'๋กœ ์ง„ํ–‰ ์ค‘์ธ refetch ์ทจ์†Œํ•˜์—ฌ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ฅผ ๋ฎ์–ด์“ฐ์ง€ ์•Š๋„๋ก ํ•จ
    await queryClient.cancleQueries({
      queryKey: ['์ฟผ๋ฆฌํ‚ค']
    })
    
    // ์ด์ „ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ด
    const preData = queryClient.getQueryData(['์ฟผ๋ฆฌํ‚ค']);
    
    // ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ
    queryClient.setQueryData(['์ฟผ๋ฆฌํ‚ค'], (prev) => !prev)
    
    return { preData }
  },
  
 // mutation ์‹คํŒจ
  onError: (err, newData, context) => {
    // onMutate๋กœ๋ถ€ํ„ฐ ๋ฐ˜ํ™˜๋œ context๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ rollback
    queryClient.setQueryData(['์ฟผ๋ฆฌํ‚ค'], context.previousData)
  },
  onSettled: () => {
    // ์„ฑ๊ณต, ์‹คํŒจ ์—ฌ๋ถ€์— ๊ด€๊ณ„์—†์ด refetch
    queryClient.invalidateQueries({queryKey['์ฟผ๋ฆฌํ‚ค']})
  }
});

๐Ÿ’ก useMutation์œผ๋กœ ์—…๋ฐ์ดํŠธ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜์˜ํ•˜๊ธฐ

invalidateQueries

  • useQuery์—์„œ ์‚ฌ์šฉ๋œ queryKey์˜ ์œ ํšจ์„ฑ์„ ์ œ๊ฑฐํ•ด์ค„ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋จ
  • queryKey์˜ ์œ ํšจ์„ฑ์„ ์ œ๊ฑฐํ•ด์ฃผ๋Š” ์ด์œ ?
    - ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ์กฐํšŒํ•˜๊ธฐ ์œ„ํ•ด์„œ (get์š”์ฒญ ๋‹ค์‹œ)
  • useQuery์—๋Š” stateTime, cacheTime์ด ์กด์žฌํ•˜์—ฌ ์—…๋ฐ์ดํŠธ ์‚ฌํ•ญ์ด ์ƒ๊ฒผ๋”๋ผ๋„ ํ•ด๋‹น ์‹œ๊ฐ„์ด ์ง€๋‚˜๊ธฐ ์ „๊นŒ์ง€๋Š” ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํ™”๋ฉด์— ๋ณด์—ฌ์คŒ
  • invalidateQueries๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€์ง€๊ณ  ์žˆ๋˜ queryKey์˜ ์œ ํšจ์„ฑ์„ ์ œ๊ฑฐํ•ด์คŒ์œผ๋กœ์จ ์บ์‹ฑ๋˜์–ด ์žˆ๋˜ ๋ฐ์ดํ„ฐ ๋Œ€์‹  ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์— ์š”์ฒญํ•จ
const queryClient = useQueryClient(); // ๋“ฑ๋ก๋œ queryClient ๊ฐ€์ ธ์˜ค๊ธฐ

const delete = useMutation(() => axios.delete(`/api/delete/${fundId}`), {
  onSuccess: () => {
    console.log('๐Ÿ‘Œ๐ŸปํŽ€๋”ฉ ์ƒ์„ฑ ์„ฑ๊ณต');
    // ์š”์ฒญ ์„ฑ๊ณต ์‹œ ํ•ด๋‹น queryKey ์œ ํšจ์„ฑ ์ œ๊ฑฐ
    queryClient.invalidateQueries('์ฟผ๋ฆฌํ‚ค')
  },
  onError: () => { console.error('โŒํŽ€๋”ฉ ์ƒ์„ฑ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ') },
  onSettled: () => { console.log('๊ฒฐ๊ณผ๋ž‘ ์ƒ๊ด€์—†์ด ๋ญ”๊ฐ€ ์‹คํ–‰๋จ') }
})

โœš References

๊ณต์‹๋ฌธ์„œ
๋ธ”๋กœ๊ทธ

profile
์‚ฌ๋ž‘์•„ ์ปดํ“จํ„ฐํ•ด ~

0๊ฐœ์˜ ๋Œ“๊ธ€