๐Ÿš€ React-Query ๊ณต์‹๋ฌธ์„œ ๋ฒˆ์—ญ

Jake_Youngยท2022๋…„ 4์›” 25์ผ
2

๋ถ€์กฑํ•œ ๊ฐœ๋ฐœ์ž๋Š” ๊ธ€์„ ๋ˆˆ์œผ๋กœ๋งŒ ์ฝ์–ด์„œ๋Š” ์ค‘์š”ํ•œ ์ง€์‹์„ ๋จธ๋ฆฌ์— ๋‚จ๊ธธ ์ˆ˜ ์—†๊ธฐ์—
react-query ๊ณต์‹ ๋ฌธ์„œ์˜ ๋‚ด์šฉ ์ค‘ ์ค‘์š”ํ•ด ๋ณด์ด๋Š” ๊ฒƒ๋“ค์„ ๋ฒˆ์—ญํ•˜๋ฉฐ ์ •๋ฆฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
์˜์—ญ ๋งŽ์•„์š”!! ํ•˜์ง€๋งŒ ์—ฌ๋Ÿฌ๋ถ„๋“ค์ด ์ฝ๊ธฐ์— ์ •ํ™•ํ•œ ๋ฒˆ์—ญ๋ณด๋‹จ ์ฝ๊ธฐ ํŽธํ•œ ๋ฒˆ์—ญ์ด ์ข‹์ง€ ์•Š์„๊นŒ์š”??

๐ŸŒŸ ๋ฉ”์ธ ํŽ˜์ด์ง€

๋ฆฌ์•กํŠธ๋ฅผ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”

React์™€ React Native์—์„œ ์ „์—ญ ์ƒํƒœ ๋ณ€๊ฒฝ ์—†์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์บ์‹ฑํ•˜๊ณ  ์—…๋ฐ์ดํŠธ ํ•˜์„ธ์š”!

  1. ์„ ์–ธ์ ์ด๋ฉด์„œ ์ž๋™ํ™” ๋œ ํŠน์ง•
    • ์†์œผ๋กœ ์ž‘์„ฑํ•˜๋Š” ๋ฐ์ดํ„ฐ ํŽ˜์นญ ๋กœ์ง์€ ์ด์ œ ๊ทธ๋งŒ
    • react query์—๊ฒŒ ์–ด๋””์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์–ผ๋งˆ๋‚˜ ์ตœ์‹ ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•œ์ง€๋งŒ ๋งํ•˜์„ธ์š”
    • ๋ณ„๋‹ค๋ฅธ ์„ค์ • ์—†์ด๋„ ๋‚˜๋จธ์ง€ ๋Œ€๋ถ€๋ถ„์„ ์šฐ๋ฆฌ๊ฐ€ ์ฒ˜๋ฆฌํ•ด์ค„๊ฒŒ์š”
  2. ๊ฐ„๋‹จํ•˜๊ณ  ์นœ์ˆ™ํ•œ ํŠน์ง•
    • ๋‹น์‹ ์ด promise๋งŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋‹น์‹ ์€ ์ด๋ฏธ react-query๋ฅผ ๋งˆ์Šคํ„ฐํ–ˆ๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
    • ์šฐ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๋• ๊ธ€๋กœ๋ฒŒ ์ƒํƒœ ๊ด€๋ฆฌ๋‚˜ ๋ฆฌ๋“€์„œ ๊ฐ™์ด ๋ณต์žกํ•œ ์„ค์ •์ด ํ•„์š” ์—†์–ด์š”
    • ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ• (๊ทธ๋ฆฌ๊ณ  ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ• ) ํ•จ์ˆ˜๋งŒ ๋˜์ ธ์ฃผ์„ธ์š”
  3. ๋‹ค์–‘ํ•œ ์กฐ์ž‘์ด ๊ฐ€๋Šฅํ•ด ๊ฐ•๋ ฅํ•œ ํŠน์ง•
    • ์ฟผ๋ฆฌ์˜ ๋ชจ๋“  observer ๊ฐ์ฒด๋งˆ๋‹ค ํŠน๋ณ„ํ•œ ์„ค์ •์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด์š”
    • ๊ฐ•๋ ฅํ•œ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ ๋“ฑ์ด ์žˆ๊ธฐ์— ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ์™€ ํ•จ๊ป˜๋ผ๋ฉด ๋ฐ์ดํ„ฐ ์กฐ์ž‘์€ ์‹์€ ์ฃฝ ๋จน๊ธฐ๋ผ๊ตฌ์š”!
    • ๊ทธ๋ ‡๋‹ค๊ณ  ๋„ˆ๋ฌด ๊ฑฑ์ •์€ ๋งˆ์„ธ์š”. ์ด๋Ÿฐ ๊ฒƒ๋“ค์€ ๋‹ค ์šฐ๋ฆฌ๊ฐ€ ์ด๋ฏธ ๊ธฐ๋ณธ ์„ธํŒ…์„ ์™„๋ฃŒํ•ด๋‘์—ˆ์œผ๋‹ˆ๊นŒ์š”!

๊ณต์‹ ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ ๊ฐ•์ขŒ๋„ ์žˆ์–ด์š”! ๋งํฌ

ํ›„์›์‚ฌ ๋ชฉ๋ก

์ ์€ ์ฝ”๋“œ์™€ ๋“œ๋ฌธ ํฌ๊ท€ ์ผ€์ด์Šค

  • ๋ณต์žกํ•œ ๊ฑฐ ์ด๊ฑฐ ์ €๊ฑฐ ๋‹ค ์ ์„ ํ•„์š”๊ฐ€ ์—†์–ด์š”!
  • ์ผ๋‹จ ํ•œ๋ฒˆ ์จ๋ณด์„ธ์š”
  • ์–ผ๋งˆ๋‚˜ ์ฝ”๋“œ๊ฐ€ ์ ์–ด์งˆ ์ˆ˜ ์žˆ๋Š”์ง€ ๊นœ์ง ๋†€๋ผ์‹ค ๊ฒ๋‹ˆ๋‹ค.

์˜ˆ์ œ์ฝ”๋“œ SandBox

๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ ํ•˜๋‚˜๋กœ ๋ชจ๋“  ๊ธฐ๋Šฅ์ด ๋”ฐ๋ผ์˜ต๋‹ˆ๋‹ค

  • (๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ๋“ค..)
  • ๋ณ‘๋ ฌ ์ฟผ๋ฆฌ ๊ธฐ๋Šฅ
  • SSR ์ง€์›

โœ… Docs ํƒญ (์‹œ์ž‘ํ•˜๊ธฐ - Getting Started)

๊ฐœ๊ด„

๊ฐœ๋ฐœ ๋™๊ธฐ

  • ๋ฆฌ์•กํŠธ๋Š” ์‚ฌ์‹ค ์ •์‹์œผ๋กœ ์ง€์›๋˜๋Š” ๋ฐ์ดํ„ฐ fetching ๋ฐฉ์‹์ด ์—†์—ˆ๋‹ค
  • ๊ทธ๋ž˜์„œ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ์ž๊ธฐ ๋งŒ์˜ ๋ฐฉ์‹์œผ๋กœ ๊ฐœ๋ฐœํ•˜๊ฑฐ๋‚˜ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ๋งŒ ํ–ˆ๋‹ค.
  • ๊ทธ๋Ÿฐ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ๋Œ€๋ถ€๋ถ„ ํ›Œ๋ฅญํ•˜๊ธด ํ–ˆ์ง€๋งŒ, ๋ถ€์กฑํ•œ ์ ๋„ ๋งŽ์•˜๋‹ค.
  • ์šฐ์„  ํด๋ผ์ด์–ธํŠธ์˜ ์ƒํƒœ๋Š” ์„œ๋ฒ„ ์ƒํƒœ์™€ ๊ทผ๋ณธ์ ์œผ๋กœ ๊ตฌ๋ณ„๋œ๋‹ค๋Š” ์ ์ด ๊ฐ€์žฅ ํฐ ๋ฌธ์ œ์˜€๋‹ค.
  • ์–ด๋–ป๊ฒŒ ์–ด๋–ป๊ฒŒ ์„œ๋ฒ„์˜ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์ปจํŠธ๋กค ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค ํ•˜๋”๋ผ๋„ ๋” ํฐ ๋ฌธ์ œ๋“ค์ด ๋งŽ์ด ์žˆ์„ ๊ฒƒ์ด๋‹ค.
  • ๋งŒ์•ฝ ์ด๋Ÿฐ ๋ฌธ์ œ๋“ค์ด ๋ณ„๋กœ ๋Œ€๋‹จํ•˜๊ฒŒ ๋Š๊ปด์ง€์ง€ ์•Š๋Š”๋‹ค๋ฉด, ๋‹น์‹ ์€ ๋ถ„๋ช…ํžˆ ๋ˆ„๊ตฌ๋‚˜๊ฐ€ ์ธ์ •ํ•˜๋Š” ํ‚น์™•์งฑ ๊ฐœ๋ฐœ์ž์ผ ๊ฒƒ์ด๋‹ค.
  • ๊ทธ๋Ÿฌ๋‚˜ ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์ด๋Š” ๋Œ€๋‹จํžˆ ์–ด๋ ค์šด ๋ฌธ์ œ์ด๋‹ค.
  • ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ๋Š” ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๋Œ€๋‹จํžˆ ํ›Œ๋ฅญํ•œ ์ตœ๊ณ ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

๋ง์€ ๋๊ณ , ์˜ˆ์ œ ์ฝ”๋“œ ๋จผ์ € ๋ณด์—ฌ์ค„๊ฒŒ!

 import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
 
 const queryClient = new QueryClient()
 
 export default function App() {
   return (
     <QueryClientProvider client={queryClient}>
       <Example />
     </QueryClientProvider>
   )
 }
 
 function Example() {
   const { isLoading, error, data } = useQuery('repoData', () =>
     fetch('https://api.github.com/repos/tannerlinsley/react-query').then(res =>
       res.json()
     )
   )
 
   if (isLoading) return 'Loading...'
 
   if (error) return 'An error has occurred: ' + error.message
 
   return (
     <div>
       <h1>{data.name}</h1>
       <p>{data.description}</p>
       <strong>๐Ÿ‘€ {data.subscribers_count}</strong>{' '}
       <strong>โœจ {data.stargazers_count}</strong>{' '}
       <strong>๐Ÿด {data.forks_count}</strong>
     </div>
   )
 }

์„ค์น˜

  • yarn add react-query
  • ์ตœ์†Œ ์š”๊ตฌ ์กฐ๊ฑด react v 16.8+

๋น ๋ฅธ ์‹คํ–‰ (quick start)

 import {
   useQuery,
   useMutation,
   useQueryClient,
   QueryClient,
   QueryClientProvider,
 } from 'react-query'
 import { getTodos, postTodo } from '../my-api'
 
 // Create a client
 const queryClient = new QueryClient()
 
 function App() {
   return (
     // Provide the client to your App
     <QueryClientProvider client={queryClient}>
       <Todos />
     </QueryClientProvider>
   )
 }
 
 function Todos() {
   // Access the client
   const queryClient = useQueryClient()
 
   // Queries
   const query = useQuery('todos', getTodos)
 
   // Mutations
   const mutation = useMutation(postTodo, {
     onSuccess: () => {
       // Invalidate and refetch
       queryClient.invalidateQueries('todos')
     },
   })
 
   return (
     <div>
       <ul>
         {query.data.map(todo => (
           <li key={todo.id}>{todo.title}</li>
         ))}
       </ul>
 
       <button
         onClick={() => {
           mutation.mutate({
             id: Date.now(),
             title: 'Do Laundry',
           })
         }}
       >
         Add Todo
       </button>
     </div>
   )
 }
 
 render(<App />, document.getElementById('root'))

๊ฐœ๋ฐœ ๋„๊ตฌ

  • ์šฐ๋ฆฌ๊ฐ€ ์ง„์งœ ์ข‹์€ ๊ฐœ๋ฐœ ๋„๊ตฌ ๋งŒ๋“ค์–ด ๋†จ๋‹ค๊ตฌ!
  • ๊ทผ๋ฐ ์•„์ง react native ์šฉ์€ ์—†์–ดใ… (22๋…„ 4์›” 25์ผ ๊ธฐ์ค€)
  • import { ReactQueryDevtools } from 'react-query/devtools'
  • ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ ๊ฐœ๋ฐœ๋„๊ตฌ๋Š” process.env.NODE_ENV === 'development'์ผ๋•Œ๋งŒ ๋ฒˆ๋“ค๋ง ๋˜๋‹ˆ๊นŒ ๋”ฐ๋กœ ๊ฑฑ์ • ๋ง๋ผ๊ณ ~
  • 'Floating Mode'๋ผ๊ณ  ํ•ด์„œ ํด๋ผ์ด์–ธํŠธ ํŽ˜์ด์ง€์— ๋„์›Œ๋‘๊ณ  ์“ธ ์ˆ˜๋„ ์žˆ์–ด~
import { ReactQueryDevtools } from 'react-query/devtools'
 
 function App() {
   return (
     <QueryClientProvider client={queryClient}>
       {/* The rest of your application */}
       <ReactQueryDevtools initialIsOpen={false} />
     </QueryClientProvider>
   )
 }
  • 'Embedded Mode'๋กœ ํ•˜๋ฉด ํŽ˜์ด์ง€์— ์ •์‹ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐœ๋ฐœ๋„๊ตฌ๋ฅผ ๋„ฃ์„ ์ˆ˜๋„ ์žˆ์–ด
 import { ReactQueryDevtoolsPanel } from 'react-query/devtools'
 
 function App() {
   return (
     <QueryClientProvider client={queryClient}>
       {/* The rest of your application */}
       <ReactQueryDevtoolsPanel style={styles} className={className} />
     </QueryClientProvider>
   )
 }

๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค๊ณผ์˜ ๋น„๊ต (SWR, Apollo ๋“ฑ)

  • ์ž์„ธํ•œ ๊ฑด ์ง์ ‘ ๋“ค์–ด๊ฐ€ ๋ณด์„ธ์š”~
  • ๋งํฌ

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ

  • ์šฐ๋ฆฌ๊บผ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค
  • ๋‹ค๋งŒ ์ตœ์†Œํ•œ 3.8 ๋ฒ„์ „๋ณด๋‹ค ๋†’์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ๊ทธ๋ฆฌ๊ณ  ๋ฆฌํ„ด ๊ฐ’์— ๋Œ€ํ•ด์„œ๋„ ์ž˜ ์ ์šฉํ•˜๋ ค๋ฉด 4.1 ๋ณด๋‹ค ๋†’์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ํƒ€์ž… ๋ณ€๊ฒฝ์€ patch๋กœ ์ทจ๊ธ‰๋˜์–ด ๊ณ„์† ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ ๋ฉ๋‹ˆ๋‹ค. (Semver ๊ธฐ์ค€)
  • ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ ํŒจํ‚ค์ง€๋ฅผ ๊ณ ์ •๋œ ๋ฒ„์ „์œผ๋กœ ์‚ฌ์šฉํ•˜์‹œ๊ธฐ๋ฅผ ์ถ”์ฒœ๋“œ๋ ค์š”
  • ์ปค์Šคํ…€ ํ›… ๋งŒ๋“ค ๋•Œ๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ..
 function useGroups() {
   return useQuery<Group[], Error>('groups', fetchGroups)
 }

GraphQL

  • ์‚ฌ์‹ค ์šฐ๋ฆฌ๋Š” ์–ด๋– ํ•œ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ Fetching ํด๋ผ์ด์–ธํŠธ์™€๋„ ๊ฐ™์ด ์“ธ ์ˆ˜ ์žˆ์–ด์š”!
  • ๊ทธ๋Ÿฐ๋ฐ ์ฃผ์˜ํ•  ์ ์ด ํ•˜๋‚˜ ์žˆ์–ด์š”
  • ์šฐ๋ฆฌ๋Š” normalized caching์€ ์ง€์›ํ•˜์ง€ ์•Š์•„์š”.
  • ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์— ํ•„์š”๊ฐ€ ์—†๋Š” ๊ธฐ๋Šฅ์ด์ง€๋งŒ ๋งŒ์•ฝ ๋‹น์‹ ์—๊ฒŒ๋Š” ์ด ๊ธฐ๋Šฅ์ด ๊ผญ ํ•„์š”ํ•˜๋‹ค๋ฉด ์ฃผ์˜ํ•˜์„ธ์š”

React Native

  • 3rd party Flipper plugin์„ ์จ๋ณด์„ธ์š” ๋งํฌ
  • ์˜จ๋ผ์ธ ์ƒํƒœ ๊ด€๋ฆฌ
 import NetInfo from '@react-native-community/netinfo'
 import { onlineManager } from 'react-query'
 
 onlineManager.setEventListener(setOnline => {
   return NetInfo.addEventListener(state => {
     setOnline(state.isConnected)
   })
 })
  • ์•ฑ ํฌ์ปค์Šค ๋•Œ Refetch
 import { focusManager } from 'react-query'
 import useAppState from 'react-native-appstate-hook'
 
 function onAppStateChange(status: AppStateStatus) {
   if (Platform.OS !== 'web') {
     focusManager.setFocused(status === 'active')
   }
 }
 
 useAppState({
   onChange: onAppStateChange,
 })
  • ์Šคํฌ๋ฆฐ ํฌ์ปค์Šค ๋•Œ ์ƒˆ๋กœ๊ณ ์นจ
 import React from 'react'
 import { useFocusEffect } from '@react-navigation/native'
 
 export function useRefreshOnFocus<T>(refetch: () => Promise<T>) {
   const firstTimeRef = React.useRef(true)
 
   useFocusEffect(
     React.useCallback(() => {
       if (firstTimeRef.current) {
          firstTimeRef.current = false;
          return;
       }
 
       refetch()
     }, [refetch])
   )
 }
profile
์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์™€ ํŒŒ์ด์ฌ ๊ทธ๋ฆฌ๊ณ  ์ปดํ“จํ„ฐ์™€ ๋„คํŠธ์›Œํฌ

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