React Query (TanStack Query)
๋ชฉ์ฐจ
2.
1. React Query๋?
์๋ฒ ๋ฐ์ดํฐ๋ฅผ React ์ปดํฌ๋ํธ์์ ์ฝ๊ฒ ๊ฐ์ ธ์ค๊ณ , ์บ์ฑํ๊ณ , ๋๊ธฐํํ๊ธฐ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
๊ธฐ์กด ๋ฐฉ์์ ๋ฌธ์
// โ ๊ธฐ์กด ๋ฐฉ์ - ๋งค๋ฒ ์ง์ ๊ด๋ฆฌํด์ผ ํ ๊ฒ๋ค
const [data, setData] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
setIsLoading(true)
fetch('/api/boards')
.then(res => res.json())
.then(data => { setData(data); setIsLoading(false) })
.catch(err => { setError(err); setIsLoading(false) })
}, [])
JavaScript
๋ณต์ฌ
React Query ๋ฐฉ์
// โ
React Query - ์บ์ฑ, ๋ก๋ฉ, ์๋ฌ, ๋ฆฌํจ์น ์๋ ์ฒ๋ฆฌ
const { data, isLoading, isError } = useQuery({
queryKey: ['boards'],
queryFn: () => fetch('/api/boards').then(res => res.json()),
})
JavaScript
๋ณต์ฌ
React Query๊ฐ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ๋ค
๊ธฐ๋ฅ | ์ค๋ช
|
์บ์ฑ | ๊ฐ์ ํค๋ก ์์ฒญ ์ ์๋ฒ ์์ฒญ ์์ด ์บ์ ๋ฐํ |
์ค๋ณต ์์ฒญ ์ ๊ฑฐ | ๋์์ ๊ฐ์ ์ฟผ๋ฆฌ ์คํ ์ ์์ฒญ 1๋ฒ๋ง ์ ์ก |
๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฆฌํจ์น | ์ฐฝ ํฌ์ปค์ค ์ ์๋์ผ๋ก ์ต์ ๋ฐ์ดํฐ ๊ฐฑ์ |
๋ก๋ฉ/์๋ฌ ์ํ | isLoading, isError ์๋ ์ ๊ณต |
ํ์ด์ง ์ดํ ํ ๋ณต๊ท | ์ด์ ๋ฐ์ดํฐ ์ฆ์ ํ์ ํ ๋ฐฑ๊ทธ๋ผ์ด๋ ๊ฐฑ์ |
์ฌ์๋ | ์คํจ ์ ์๋ ์ฌ์๋ (ํ์ ์ค์ ๊ฐ๋ฅ) |
2. ์ค์น
npm install @tanstack/react-query @tanstack/react-query-devtools
Bash
๋ณต์ฌ
ํจํค์ง | ์ญํ |
@tanstack/react-query | ํต์ฌ ๊ธฐ๋ฅ (useQuery, useMutation ๋ฑ) |
@tanstack/react-query-devtools | ๊ฐ๋ฐ์ฉ ์บ์ ์ํ ์๊ฐํ ๋๊ตฌ |
3. QueryClient - ์บ์ ์ ์ฅ์
QueryClient๋ React Query์ ์บ์ ์ ์ฅ์์ด์ ์ค์ ์ปจํ
์ด๋๋ค.
์ฑ ์ ์ฒด์์ ํ๋์ ์ธ์คํด์ค๋ง ์์ฑํ๋ค.
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1, // ๋คํธ์ํฌ ์คํจ ์ ์ฌ์๋ ํ์ (๊ธฐ๋ณธ๊ฐ: 3)
staleTime: 1000 * 30, // ๋ฐ์ดํฐ๋ฅผ "์ ์ "ํ๋ค๊ณ ๋ณด๋ ์๊ฐ(ms) (๊ธฐ๋ณธ๊ฐ: 0)
},
},
})
JavaScript
๋ณต์ฌ
defaultOptions.queries ์ฃผ์ ์ต์
์ต์
| ํ์
| ๊ธฐ๋ณธ๊ฐ | ์ค๋ช
|
retry | number \| boolean | 3 | ์คํจ ์ ์ฌ์๋ ํ์. false๋ฉด ์ฌ์๋ ์ ํจ |
staleTime | number | 0 | ์บ์๋ฅผ "์ ์ "ํ๋ค๊ณ ๋ณด๋ ์๊ฐ(ms). ์ด ์๊ฐ ์ด๋ด๋ฉด ์๋ฒ ์ฌ์์ฒญ ์์ |
gcTime | number | 5 * 60 * 1000 | ์ฌ์ฉ๋์ง ์๋ ์บ์๋ฅผ ๋ฉ๋ชจ๋ฆฌ์์ ์ ์งํ๋ ์๊ฐ(ms) |
refetchOnWindowFocus | boolean | true | ์ฐฝ ํฌ์ปค์ค ์ ์๋ ๋ฆฌํจ์น ์ฌ๋ถ |
refetchOnMount | boolean | true | ์ปดํฌ๋ํธ ๋ง์ดํธ ์ ๋ฆฌํจ์น ์ฌ๋ถ |
์ด ํ๋ก์ ํธ ์ค์
// src/main.jsx
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1, // ๊ธฐ๋ณธ 3ํ โ 1ํ๋ก ์ค์ฌ ๋น ๋ฅธ ์๋ฌ ๊ฐ์ง
staleTime: 1000 * 30, // 30์ด๊ฐ ์บ์ ์ ํจ (๊ฒ์ํ ํน์ฑ์ ์์ฃผ ๋ฐ๋์ง ์์)
},
},
})
JavaScript
๋ณต์ฌ
4. QueryClientProvider - ์ ์ญ ๊ณต๊ธ์
QueryClientProvider๋ React Context๋ฅผ ์ฌ์ฉํด ์ฑ ์ ์ฒด์ QueryClient๋ฅผ ๊ณต๊ธํ๋ ์ปดํฌ๋ํธ๋ค.
useQuery, useMutation, useQueryClient ๋ฑ์ ํ
์ด ์ด Context์์ QueryClient๋ฅผ ๊ฐ์ ธ์จ๋ค.
// src/main.jsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient({ /* ์ค์ */ })
createRoot(document.getElementById('root')).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</StrictMode>
)
JavaScript
๋ณต์ฌ
๊ตฌ์กฐ ์๋ฆฌ
<QueryClientProvider client={queryClient}> โ QueryClient๋ฅผ Context์ ๋ฑ๋ก
<App>
<ListPage>
<List>
useQuery(...) โ Context์์ QueryClient๋ฅผ ๊บผ๋ด ์บ์ ์กฐํ
</List>
</ListPage>
</App>
</QueryClientProvider>
Plain Text
๋ณต์ฌ
์ฃผ์์ฌํญQueryClientProvider ๋ฐ์์ useQuery ๋ฑ์ ํธ์ถํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
<App />์ ๋ฐ๋์ <QueryClientProvider> ์์ ๋ฃ์ด์ผ ํ๋ค.
5. useQuery - ๋ฐ์ดํฐ ์กฐํ
์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด์ค๋ ํ
. GET ์์ฒญ์ ์ฃผ๋ก ์ฌ์ฉํ๋ค.
๊ธฐ๋ณธ ๊ตฌ์กฐ
const { data, isLoading, isError, error } = useQuery({
queryKey: ['๊ณ ์ ํ ํค'], // ์บ์ ์๋ณ์
queryFn: () => fetch(...), // ์ค์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ํจ์
})
JavaScript
๋ณต์ฌ
queryKey - ์บ์ ์๋ณ์
queryKey๋ ์บ์๋ฅผ ๊ตฌ๋ถํ๋ ๋ฐฐ์ด ํํ์ ๊ณ ์ ํค๋ค.
ํค๊ฐ ๋ฐ๋๋ฉด ์๋์ผ๋ก ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ค.
// ๋จ์ ํค - ๋ณํ์ง ์๋ ๋ฐ์ดํฐ
queryKey: ['boards']
// ํ๋ผ๋ฏธํฐ ํฌํจ - ํ์ด์ง๋ง๋ค ๋ค๋ฅธ ์บ์
queryKey: ['boards', page, size] // ['boards', 1, 10], ['boards', 2, 10] ๊ฐ๊ฐ ๋ณ๋ ์บ์
// ID ํฌํจ - ๊ฒ์๊ธ๋ง๋ค ๋ค๋ฅธ ์บ์
queryKey: ['board', id] // ['board', 1], ['board', 2] ๊ฐ๊ฐ ๋ณ๋ ์บ์
JavaScript
๋ณต์ฌ
queryKey ์ค๊ณ ์์น
ํ๋ผ๋ฏธํฐ๊ฐ ๋ฌ๋ผ์ง๋ฉด ๋ค๋ฅธ ๋ฐ์ดํฐ๊ฐ ๋์ค๋ ๊ฒฝ์ฐ ๋ฐ๋์ ํค์ ํฌํจํ๋ค.
queryFn - ๋ฐ์ดํฐ ์์ฒญ ํจ์
queryFn์ Promise๋ฅผ ๋ฐํํด์ผ ํ๋ค.
// axios๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ - .then()์ผ๋ก ์ค์ ๋ฐ์ดํฐ๋ง ์ถ์ถ
queryFn: () => boardsApi.list(page, size).then((res) => res.data)
// fetch๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
queryFn: () => fetch('/api/boards').then(res => res.json())
// async/await ๋ฐฉ์
queryFn: async () => {
const res = await boardsApi.list(page, size)
return res.data
}
JavaScript
๋ณต์ฌ
๋ฐํ๊ฐ
const {
data, // queryFn์ด ๋ฐํํ ๋ฐ์ดํฐ (์ด๊ธฐ์๋ undefined)
isLoading, // ์ฒ์ ๋ก๋ฉ ์ค (๋ฐ์ดํฐ ์๊ณ fetch ์ค)
isFetching, // ๋ฐฑ๊ทธ๋ผ์ด๋ ํฌํจ ๋ชจ๋ fetch ์ค
isError, // ์๋ฌ ๋ฐ์ ์ฌ๋ถ
error, // ์๋ฌ ๊ฐ์ฒด
isSuccess, // ์ฑ๊ณต ์ฌ๋ถ
refetch, // ์๋์ผ๋ก ๋ค์ ๊ฐ์ ธ์ค๊ธฐ
status, // 'pending' | 'error' | 'success'
} = useQuery({ queryKey, queryFn })
JavaScript
๋ณต์ฌ
isLoading vs isFetching
โข
isLoading : ์บ์ ๋ฐ์ดํฐ๊ฐ ์๊ณ ์ฒ์ ์์ฒญ ์ค์ผ ๋๋ง true
โข
isFetching : ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฆฌํจ์น ํฌํจ ์ด๋ค fetch๋ ์งํ ์ค์ด๋ฉด true
์ฃผ์ ์ต์
useQuery({
queryKey: ['boards', page],
queryFn: () => boardsApi.list(page),
enabled: !!id, // false๋ฉด ์ฟผ๋ฆฌ ์คํ ์ ํจ (์กฐ๊ฑด๋ถ ์คํ)
staleTime: 1000 * 60, // 1๋ถ๊ฐ ์ ์ (์ ์ญ ์ค์ ์ค๋ฒ๋ผ์ด๋)
placeholderData: (prev) => prev, // ํค ๋ณ๊ฒฝ ์ ์ด์ ๋ฐ์ดํฐ๋ฅผ ์์๋ก ํ์
select: (data) => data.list, // ๋ฐํ ๋ฐ์ดํฐ ๋ณํ/์ ํ
retry: false, // ์คํจ ์ ์ฌ์๋ ์ ํจ
})
JavaScript
๋ณต์ฌ
enabled - ์กฐ๊ฑด๋ถ ์คํ
// id๊ฐ ์์ ๋๋ง ์์ฒญ (useBoard.js)
const { data } = useQuery({
queryKey: ['board', id],
queryFn: () => boardsApi.select(id).then(res => res.data),
enabled: !!id, // id๊ฐ undefined/null์ด๋ฉด ์์ฒญ ์ ํจ
})
JavaScript
๋ณต์ฌ
placeholderData - ํ์ด์ง ์ ํ ์ ๊น๋นก์ ๋ฐฉ์ง
// useBoards.js - ํ์ด์ง ์ด๋ ์ ์ด์ ๋ชฉ๋ก์ ์ ์งํด ํ๋ฉด ๊น๋นก์ ๋ฐฉ์ง
useQuery({
queryKey: ['boards', page, size],
queryFn: () => boardsApi.list(page, size).then(res => res.data),
placeholderData: (previousData) => previousData,
// โ ์ ๋ฐ์ดํฐ ๋์ฐฉ ์ ๊น์ง ์ด์ ํ์ด์ง ๋ฐ์ดํฐ๋ฅผ ์์ ํ์
})
JavaScript
๋ณต์ฌ
6. useMutation - ๋ฐ์ดํฐ ๋ณ๊ฒฝ
์๋ฒ ๋ฐ์ดํฐ๋ฅผ ์์ฑ/์์ /์ญ์ ํ๋ ํ
. POST, PUT, DELETE ์์ฒญ์ ์ฌ์ฉํ๋ค.
๊ธฐ๋ณธ ๊ตฌ์กฐ
const mutation = useMutation({
mutationFn: (๋ณ์) => ์๋ฒ์์ฒญํจ์(๋ณ์),
onSuccess: (data) => { /* ์ฑ๊ณต ํ ์ฒ๋ฆฌ */ },
onError: (error) => { /* ์คํจ ํ ์ฒ๋ฆฌ */ },
})
// ํธ์ถ
mutation.mutate(์ ๋ฌํ ๊ฐ)
JavaScript
๋ณต์ฌ
mutationFn
// ๋จ์ ์ธ์
mutationFn: (id) => boardsApi.remove(id)
// ๊ฐ์ฒด ์ธ์ (์ฌ๋ฌ ๊ฐ ์ ๋ฌ)
mutationFn: ({ data, headers }) => boardsApi.insert(data, headers)
// ๋ฐฐ์ด ์ธ์
mutationFn: (idList) => filesApi.removeFiles(idList)
JavaScript
๋ณต์ฌ
์ฝ๋ฐฑ ์ต์
useMutation({
mutationFn: (data) => boardsApi.insert(data),
onSuccess: (responseData, variables, context) => {
// ์์ฒญ ์ฑ๊ณต ์ ํธ์ถ
// responseData: ์๋ฒ ์๋ต๊ฐ
// variables: mutate()์ ์ ๋ฌํ ๊ฐ
},
onError: (error, variables, context) => {
// ์์ฒญ ์คํจ ์ ํธ์ถ
console.error(error.message)
},
onSettled: (data, error, variables) => {
// ์ฑ๊ณต/์คํจ ๋ฌด๊ดํ๊ฒ ํญ์ ํธ์ถ (finally์ ์ ์ฌ)
},
onMutate: async (variables) => {
// ์์ฒญ ์ง์ ์ ํธ์ถ (๋๊ด์ ์
๋ฐ์ดํธ์ ํ์ฉ)
},
})
JavaScript
๋ณต์ฌ
๋ฐํ๊ฐ
const {
mutate, // ๋ฎคํ
์ด์
์คํ ํจ์ (๋น๋๊ธฐ ๊ฒฐ๊ณผ ๋ฌด์)
mutateAsync, // ๋ฎคํ
์ด์
์คํ ํจ์ (Promise ๋ฐํ, await ๊ฐ๋ฅ)
isPending, // ์์ฒญ ์งํ ์ค
isSuccess, // ์์ฒญ ์ฑ๊ณต
isError, // ์์ฒญ ์คํจ
error, // ์๋ฌ ๊ฐ์ฒด
data, // ์ฑ๊ณต ์๋ต ๋ฐ์ดํฐ
reset, // ์ํ ์ด๊ธฐํ
} = useMutation({ mutationFn })
JavaScript
๋ณต์ฌ
mutate vs mutateAsync
// mutate - fire-and-forget, ์๋ฌ๋ onError๋ก ์ฒ๋ฆฌ
mutation.mutate(data)
// mutateAsync - Promise ๋ฐํ, try/catch ๊ฐ๋ฅ
try {
const result = await mutation.mutateAsync(data)
console.log(result)
} catch (error) {
console.error(error)
}
JavaScript
๋ณต์ฌ
isPending - ๋ฒํผ ๋นํ์ฑํ
// ์ ์ฅ ์ค ๋ฒํผ ๋นํ์ฑํ (useBoardMutations.js)
return {
insertBoard: (data, headers) => insertMutation.mutate({ data, headers }),
isInserting: insertMutation.isPending, // ์์ฒญ ์ค์ด๋ฉด true
}
// ์ปดํฌ๋ํธ์์
<button type="submit" disabled={isInserting}>
{isInserting ? '์ ์ฅ ์ค...' : '์ ์ฅ'}
</button>
JavaScript
๋ณต์ฌ
7. useQueryClient - ์บ์ ์ง์ ์กฐ์
useQueryClient๋ QueryClient ์ธ์คํด์ค์ ์ง์ ์ ๊ทผํ๋ ํ
์ด๋ค.
์ฃผ๋ก ๋ฎคํ
์ด์
์ฑ๊ณต ํ ์บ์๋ฅผ ๋ฌดํจํํ๊ฑฐ๋ ์ง์ ์
๋ฐ์ดํธํ ๋ ์ฌ์ฉํ๋ค.
import { useQueryClient } from '@tanstack/react-query'
const queryClient = useQueryClient()
JavaScript
๋ณต์ฌ
invalidateQueries - ์บ์ ๋ฌดํจํ (๊ฐ์ฅ ์์ฃผ ์ฌ์ฉ)
์บ์๋ฅผ "๋ง๋ฃ๋จ"์ผ๋ก ํ์ํด ๋ค์ ๋ ๋๋ง ์ ์๋ฒ์์ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ํ๋ค.
// ํน์ ํค ๋ฌดํจํ
queryClient.invalidateQueries({ queryKey: ['boards'] })
// ํน์ ํค + ํ๋ผ๋ฏธํฐ ๋ฌดํจํ
queryClient.invalidateQueries({ queryKey: ['board', id] })
// 'boards'๋ก ์์ํ๋ ๋ชจ๋ ์บ์ ๋ฌดํจํ (๋ถ๋ถ ๋งค์นญ)
queryClient.invalidateQueries({ queryKey: ['boards'] })
// โ ['boards'], ['boards', 1, 10], ['boards', 2, 10] ๋ชจ๋ ๋ฌดํจํ
JavaScript
๋ณต์ฌ
getQueryData / setQueryData - ์บ์ ์ฝ๊ธฐ/์ฐ๊ธฐ
// ํ์ฌ ์บ์ ์ฝ๊ธฐ (์๋ฒ ์์ฒญ ์์)
const boards = queryClient.getQueryData(['boards', 1, 10])
// ์บ์ ์ง์ ์
๋ฐ์ดํธ (์๋ฒ ์์ฒญ ์์ด UI ์ฆ์ ๋ฐ์ - ๋๊ด์ ์
๋ฐ์ดํธ)
queryClient.setQueryData(['board', id], (oldData) => ({
...oldData,
board: { ...oldData.board, title: newTitle }
}))
JavaScript
๋ณต์ฌ
prefetchQuery - ๋ฏธ๋ฆฌ ์์ฒญ
// ๋ค์ ํ์ด์ง๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ์ ธ์ ์บ์์ ์ ์ฅ
await queryClient.prefetchQuery({
queryKey: ['boards', nextPage],
queryFn: () => boardsApi.list(nextPage).then(res => res.data),
})
JavaScript
๋ณต์ฌ
์ด ํ๋ก์ ํธ์์์ ๋ฌดํจํ ํจํด
// useBoardMutations.js
const queryClient = useQueryClient()
// ๊ธ ๋ฑ๋ก ์ฑ๊ณต โ ๋ชฉ๋ก ์บ์ ๋ฌดํจํ
onSuccess: async () => {
queryClient.invalidateQueries({ queryKey: ['boards'] })
// โ ๋ชฉ๋ก ํ์ด์ง ์ฌ์ง์
์ ์ต์ ๋ชฉ๋ก ๋ก๋
}
// ๊ธ ์์ ์ฑ๊ณต โ ๋ชฉ๋ก + ๋จ๊ฑด ์บ์ ๋ฌดํจํ
onSuccess: async () => {
queryClient.invalidateQueries({ queryKey: ['boards'] }) // ๋ชฉ๋ก
queryClient.invalidateQueries({ queryKey: ['board', id] }) // ์์ธ
}
// ํ์ผ ์ญ์ ์ฑ๊ณต โ ํด๋น ๊ฒ์๊ธ ์บ์ ๋ฌดํจํ
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['board', id] })
// โ ํ์ผ ๋ชฉ๋ก์ด ์ฆ์ ๊ฐฑ์ ๋จ
}
JavaScript
๋ณต์ฌ
8. ReactQueryDevtools - ๋๋ฒ๊น ๋๊ตฌ
๊ฐ๋ฐ ํ๊ฒฝ์์ ์บ์ ์ํ๋ฅผ ์๊ฐ์ ์ผ๋ก ํ์ธํ๋ ํ๋กํ
ํจ๋์ด๋ค.
ํ๋ก๋์
๋น๋์๋ ์๋์ผ๋ก ํฌํจ๋์ง ์๋๋ค.
// src/main.jsx
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
{/* โ ํ๋ฉด ์ฐํ๋จ์ ํ๋กํ
๋ฒํผ์ผ๋ก ํ์ */}
</QueryClientProvider>
JavaScript
๋ณต์ฌ
ํ์ธ ๊ฐ๋ฅํ ์ ๋ณด
ํญ๋ชฉ | ์ค๋ช
|
์ฟผ๋ฆฌ ๋ชฉ๋ก | ํ์ฌ ์บ์์ ์ ์ฅ๋ ๋ชจ๋ ์ฟผ๋ฆฌ ํค |
์ฟผ๋ฆฌ ์ํ | fresh / stale / fetching / inactive |
์บ์ ๋ฐ์ดํฐ | ์ฟผ๋ฆฌ๋ณ ์ค์ ์ ์ฅ๋ ๋ฐ์ดํฐ |
๋ง์ง๋ง ๊ฐฑ์ ์๊ฐ | ์ธ์ ๋ง์ง๋ง์ผ๋ก ์๋ฒ์์ ๊ฐ์ ธ์๋์ง |
์๋ ๋ฆฌํจ์น | Devtools์์ ์ง์ ๋ฆฌํจ์น ์คํ ๊ฐ๋ฅ |
์ฟผ๋ฆฌ ์ํ ์์
์์ | ์ํ | ์๋ฏธ |
์ด๋ก | fresh | ์ ์ ํ ๋ฐ์ดํฐ (staleTime ์ด๋ด) |
๋
ธ๋ | stale | ๋ง๋ฃ๋จ (๋ค์ ์ ๊ทผ ์ ๋ฆฌํจ์น ์์ ) |
ํ๋ | fetching | ํ์ฌ ์๋ฒ์์ ๊ฐ์ ธ์ค๋ ์ค |
ํ์ | inactive | ์ฌ์ฉ ์ค์ธ ์ปดํฌ๋ํธ ์์ (gcTime ํ ์ญ์ ) |
9. ์บ์ ๋ผ์ดํ์ฌ์ดํด
์ปดํฌ๋ํธ ๋ง์ดํธ
โ
โผ
์บ์์ ๋ฐ์ดํฐ ์์?
โโ ์์ โโโบ ์๋ฒ ์์ฒญ โ ๋ฐ์ดํฐ ์ ์ฅ โ isLoading: true ๋์ ๋ก๋ฉ ํ์
โ
โโ ์์
โ
โโ fresh? (staleTime ์ด๋ด)
โ โโ ์บ์ ์ฆ์ ๋ฐํ (์๋ฒ ์์ฒญ ์ ํจ)
โ
โโ stale? (staleTime ์ด๊ณผ)
โโ ์บ์ ์ฆ์ ๋ฐํ + ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์๋ฒ ์ฌ์์ฒญ (isFetching: true)
โ
โโ ์ ๋ฐ์ดํฐ ๋์ฐฉ โ ์บ์ ๊ต์ฒด โ UI ๊ฐฑ์
Plain Text
๋ณต์ฌ
staleTime vs gcTime
๋ฐ์ดํฐ ๋์ฐฉ
โ
โโโ staleTime ์ด๋ด โ "fresh" (์ฌ์์ฒญ ์ ํจ)
โ
โโโ staleTime ์ด๊ณผ โ "stale" (๋ค์ ์ ๊ทผ ์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ฌ์์ฒญ)
์ปดํฌ๋ํธ ์ธ๋ง์ดํธ
โ
โโโ gcTime ์ด๋ด โ ์บ์ ์ ์ง (๋ค์ ๋ง์ดํธ ์ ์ฆ์ ์ฌ์ฉ)
gcTime ์ด๊ณผ โ ์บ์ ์ญ์ (๋ค์ ๋ง์ดํธ ์ ์ ์์ฒญ)
Plain Text
๋ณต์ฌ
10. ์ด ํ๋ก์ ํธ์์์ ํ์ฉ ํจํด
์ ์ฒด ๊ตฌ์กฐ
main.jsx
QueryClient (retry: 1, staleTime: 30s)
QueryClientProvider
App
โโ ๊ฐ ํ์ด์ง โ ์ปดํฌ๋ํธ โ ์ปค์คํ
ํ
โโ useBoards(page, size) โ useQuery(['boards', page, size])
โโ useBoard(id) โ useQuery(['board', id])
โโ useBoardMutations(id) โ useMutation + useQueryClient
Plain Text
๋ณต์ฌ
๋ฐ์ดํฐ ํ๋ฆ - ๋ชฉ๋ก ์กฐํ
ListPage ๋ง์ดํธ
โโ useBoards(1, 10) ํธ์ถ
โโ useQuery({ queryKey: ['boards', 1, 10], ... })
โโ ์บ์ ์์ โ boardsApi.list(1, 10) ํธ์ถ โ GET /api/boards?page=1&size=10
โ isLoading: true โ ๋ก๋ฉ ํ์
โ ์๋ต ๋์ฐฉ โ ์บ์ ์ ์ฅ โ UI ํ์
โ
โโ ์บ์ ์์ (30์ด ์ด๋ด) โ ์ฆ์ ๋ฐํ, ์๋ฒ ์์ฒญ ์์
Plain Text
๋ณต์ฌ
๋ฐ์ดํฐ ํ๋ฆ - ๊ธ ๋ฑ๋ก ํ ๋ชฉ๋ก ๊ฐฑ์
Insert ์ปดํฌ๋ํธ
โโ onSubmit โ insertBoard(formData) ํธ์ถ
โโ insertMutation.mutate({ data: formData, headers })
โโ boardsApi.insert() โ POST /api/boards
โโ onSuccess:
โโ queryClient.invalidateQueries(['boards'])
โ โ
โ ['boards', 1, 10] ์บ์ "๋ง๋ฃ"๋ก ํ์
โ
โโ Swal ์ฑ๊ณต ์๋ฆผ
โโ navigate('/boards')
โ
ListPage ๋ง์ดํธ
โโ useBoards() ์ฌ์คํ
โโ ์บ์ ๋ง๋ฃ โ ์๋ฒ ์ฌ์์ฒญ โ ์ต์ ๋ชฉ๋ก ํ์
Plain Text
๋ณต์ฌ
์ปค์คํ ํ ์ผ๋ก ๋ถ๋ฆฌํ๋ ์ด์
โ ์ปดํฌ๋ํธ ์์ ์ง์ ์์ฑ
List.jsx โ useQuery ๋ก์ง ํฌํจ
Update.jsx โ useMutation ๋ก์ง ํฌํจ
โ ๋ก์ง์ด ์ปดํฌ๋ํธ์ ์์ฌ ์ฌ์ฌ์ฉ ๋ถ๊ฐ
โ
์ปค์คํ
ํ
์ผ๋ก ๋ถ๋ฆฌ
useBoards.js โ ๋ชฉ๋ก ์กฐํ ๋ก์ง ์บก์ํ
useBoard.js โ ๋จ๊ฑด ์กฐํ ๋ก์ง ์บก์ํ
useBoardMutations.js โ CRUD ๋ก์ง ์บก์ํ
โ ์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
โ ํ
์คํธ ์ฉ์ด
โ ์ปดํฌ๋ํธ๋ UI์๋ง ์ง์ค
Plain Text
๋ณต์ฌ
์ค์ ์ฝ๋ ์์ ๋น๊ต
// useBoards.js
export const useBoards = (page = 1, size = 10) => {
const { data, isLoading, isError } = useQuery({
queryKey: ['boards', page, size], // ํ์ด์ง๋ง๋ค ๋ณ๋ ์บ์
queryFn: () => boardsApi.list(page, size).then(res => res.data),
placeholderData: (prev) => prev, // ํ์ด์ง ์ด๋ ์ ์ด์ ๋ฐ์ดํฐ ์ ์ง
})
return { list: data?.list ?? [], pagination: data?.pagination ?? {}, isLoading, isError }
}
// useBoard.js
export const useBoard = (id) => {
const { data, isLoading, isError, refetch } = useQuery({
queryKey: ['board', id], // id๋ง๋ค ๋ณ๋ ์บ์
queryFn: () => boardsApi.select(id).then(res => res.data),
enabled: !!id, // id ์์ผ๋ฉด ์์ฒญ ์ ํจ
})
return { board: data?.board ?? null, fileList: data?.fileList ?? [], isLoading, isError, refetch }
}
// useBoardMutations.js
export const useBoardMutations = (id) => {
const queryClient = useQueryClient()
const insertMutation = useMutation({
mutationFn: ({ data, headers }) => boardsApi.insert(data, headers),
onSuccess: async () => {
queryClient.invalidateQueries({ queryKey: ['boards'] }) // ๋ชฉ๋ก ๊ฐฑ์
await successAlert('๋ฑ๋ก ์ฑ๊ณต', '๊ฒ์๊ธ ๋ฑ๋ก์ด ์๋ฃ๋์์ต๋๋ค.')
navigate('/boards')
},
})
return {
insertBoard: (data, headers) => insertMutation.mutate({ data, headers }),
isInserting: insertMutation.isPending,
}
}
JavaScript
๋ณต์ฌ
๋น ๋ฅธ ์ฐธ์กฐ
@tanstack/react-query
โโ QueryClient โ ์บ์ ์ ์ฅ์, ์ ์ญ ์ค์
โโ QueryClientProvider โ Context๋ก ์ฑ ์ ์ฒด์ ๊ณต๊ธ
โโ useQuery โ ๋ฐ์ดํฐ ์ฝ๊ธฐ (GET)
โ โโ queryKey โ ์บ์ ์๋ณ์ (๋ฐฐ์ด)
โ โโ queryFn โ ์๋ฒ ์์ฒญ ํจ์
โ โโ enabled โ ์กฐ๊ฑด๋ถ ์คํ
โ โโ placeholderData โ ์ด์ ๋ฐ์ดํฐ ์์ ํ์
โโ useMutation โ ๋ฐ์ดํฐ ์ฐ๊ธฐ (POST/PUT/DELETE)
โ โโ mutationFn โ ์๋ฒ ์์ฒญ ํจ์
โ โโ onSuccess โ ์ฑ๊ณต ์ฝ๋ฐฑ
โ โโ onError โ ์คํจ ์ฝ๋ฐฑ
โ โโ isPending โ ์์ฒญ ์งํ ์ค ์ฌ๋ถ
โโ useQueryClient โ QueryClient ์ธ์คํด์ค ์ ๊ทผ
โ โโ invalidateQueries โ ์บ์ ๋ฌดํจํ (๊ฐ์ฅ ์์ฃผ ์ฌ์ฉ)
โ โโ setQueryData โ ์บ์ ์ง์ ์์
โ โโ getQueryData โ ์บ์ ์ฝ๊ธฐ
โโ ReactQueryDevtools โ ๊ฐ๋ฐ์ฉ ์บ์ ์ํ ์๊ฐํ
Plain Text
๋ณต์ฌ




