Overview
TanStack Query(= React Query)는 웹 애플리케이션에서 서버 상태 fetching, caching, 동기화 및 업데이트를 쉽게 만듭니다.
대부분의 기존 상태 관리 라이브러리는 클라이언트 상태 작업에 적합하지만 비동기 또는 서버 상태 작업에는 적합하지 않습니다.
서버 상태가 완전히 다르기 때문입니다.
애플리케이션에서 서버 상태의 특성을 파악하고 나면 더 많은 문제가 발생합니다. 예를 들면 다음과 같습니다.
-
캐싱
-
동일한 데이터에 대한 여러 요청을 단일 요청으로 중복 제거
-
백그라운드에서 “out of date”한 데이터 업데이트 데이터가 “out of date”된 시점 알기
-
데이터 업데이트를 최대한 신속하게 반영
-
pagination 및 lazy loading 데이터와 같은 성능 최적화
-
서버 상태의 메모리 및 가비지 수집 관리
React Query는 서버 상태를 관리하기 위한 최고의 라이브러리 중 하나입니다.
zero-config로 즉시 사용 가능하며 애플리케이션이 커짐에 따라 원하는 대로 사용자 정의할 수 있습니다.
설치
$ npm i @tanstack/react-query
# or
$ pnpm add @tanstack/react-query
# or
$ yarn add @tanstack/react-query
React Query는 React v16.8+와 호환되며 ReactDOM 및 React Native와 함께 작동합니다.
Queries
Query는 unique key에 연결된 데이터의 비동기 소스에 대한 선언적 종속성입니다.
Promise 기반 메서드(GET 및 POST 메서드 포함)와 함께 쿼리를 사용하여 서버에서 데이터를 가져올 수 있습니다.
(메서드가 서버의 데이터를 수정하는 경우 Mutations를 사용하는 것이 좋습니다.)
import { useQuery } from '@tanstack/react-query'
function App() {
const info = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList
})
}
Query Keys
TanStack Query는 Key를 기반으로 쿼리 caching을 관리합니다. 쿼리 키는 최상위 수준의 배열이어야 하며 단일 문자열이 포함된 배열처럼 단순하거나 많은 문자열 및 중첩 개체의 배열처럼 복잡할 수 있습니다.
쿼리 키가 직렬화 가능하고 쿼리 데이터에 고유한 한 사용할 수 있습니다!
function Todos({ todoId }) {
const result = useQuery({
queryKey: ['todos', todoId],
queryFn: () => fetchTodoById(todoId),
})
}})
Array Keys with variables
쿼리가 데이터를 unique하게 설명하기 위해 추가 정보가 필요한 경우 문자열과 serializable한 여러 object를 사용할 수 있습니다.
// An individual todo
useQuery({ queryKey: ['todo', 5], ... })
// An individual todo in a "preview" format
useQuery({ queryKey: ['todo', 5, { preview: true }], ...})
// A list of todos that are "done"
useQuery({ queryKey: ['todos', { type: 'done' }], ... })
Query Keys are hashed deterministically!
즉, object의 key 순서에 관계없이 다음 쿼리는 모두 동일한 것으로 간주됩니다.
useQuery({ queryKey: ['todos', { status, page }], ... })
useQuery({ queryKey: ['todos', { page, status }], ...})
useQuery({ queryKey: ['todos', { page, status, other: undefined }], ... })
그러나 다음 쿼리 키는 동일하지 않습니다. 배열 항목 순서가 중요합니다!
useQuery({ queryKey: ['todos', status, page], ... })
useQuery({ queryKey: ['todos', page, status], ...})
useQuery({ queryKey: ['todos', undefined, page, status], ...})
Query Function
Query function은 문자 그대로 Promise를 반환하는 모든 함수가 될 수 있습니다. 반환되는 Promise는 데이터를 resolve하거나 error를 throw해야 합니다.
다음은 모두 유효한 쿼리 기능 구성입니다.
useQuery({
queryKey: ['todos'],
queryFn: fetchAllTodos
})
useQuery({
queryKey: ['todos', todoId],
queryFn: () => fetchTodoById(todoId)
})
useQuery({
queryKey: ['todos', todoId],
queryFn: async () => {
const data = await fetchTodoById(todoId)
return data
},
})
useQuery({
queryKey: ['todos', todoId],
queryFn: ({ queryKey }) => fetchTodoById(queryKey[1]),
})
Query가 error가 있는지 확인하려면 query function이 reject된 Promise을 throw하거나 return 해야 합니다.
const { error } = useQuery({
queryKey: ['todos', todoId],
queryFn: async () => {
if (somethingGoesWrong) {
throw new Error('Oh no!')
}
if (somethingElseGoesWrong) {
return Promise.reject(new Error('Oh no!'))
}
return data
},
})
axios 또는 graphql-request와 같은 대부분은 실패한 HTTP 호출에 대해 자동으로 오류를 발생시키지만 fetch와 같은 일부 유틸리티는 기본적으로 오류를 발생시키지 않습니다.
그런 경우에는 직접 던져야 합니다.
useQuery({
queryKey: ['todos', todoId],
queryFn: async () => {
const response = await fetch('/todos/' + todoId)
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
},
})
Mutations
Mutations는 일반적으로 데이터를 create/update/delete를 수행하는 데 사용됩니다.
이를 위해 TanStack Query는 useMutation hook를 내보냅니다.
function App() {
const mutation = useMutation({
mutationFn: (newTodo) => {
return axios.post('/todos', newTodo)
},
})
return (
<div>
{mutation.isLoading ? (
'Adding todo...'
) : (
<>
{mutation.isError ? (
<div>An error occurred: {mutation.error.message}</div>
) : null}
{mutation.isSuccess ? <div>Todo added!</div> : null}
<button
onClick={() => {
mutation.mutate({ id: new Date(), title: 'Do Laundry' })
}}
>
Create Todo
</button>
</>
)}
</div>
)
}
❗️ mutate 함수는 비동기 함수이므로 React 16 및 이전 버전의 이벤트 콜백에서 직접 사용할 수 없습니다.
onSubmit에서 이벤트에 액세스해야 하는 경우 다른 함수에서 mutate를 래핑해야 합니다.
이는 React 이벤트 풀링 때문입니다.
❌
const CreateTodo = () => {
const mutation = useMutation({
mutationFn: (event) => {
event.preventDefault()
return fetch('/api', new FormData(event.target))
},
})
return <form onSubmit={mutation.mutate}>...</form>
}
✅
const CreateTodo = () => {
const mutation = useMutation({
mutationFn: (formData) => {
return fetch('/api', formData)
},
})
const onSubmit = (event) => {
event.preventDefault()
mutation.mutate(new FormData(event.target))
}
return <form onSubmit={onSubmit}>...</form>
}
mutateAsync
mutate 대신 mutateAsync를 사용하여 성공 시 resolve되거나 error가 발생하는 Promise를 가져옵니다.
const mutation = useMutation({ mutationFn: addTodo })
try {
const todo = await mutation.mutateAsync(todo)
console.log(todo)
} catch (error) {
console.error(error)
} finally {
console.log('done')
}
Important Default
기본적으로 TanStack Query는 기본값으로 구성됩니다.
때로 이러한 기본값은 개발자를 당황하게 만들거나 사용자가 모르는 경우 학습/디버깅을 어렵게 만들 수 있습니다.
다음 사항을 염두에 두십시오.
🌟 기본적으로 useQuery 또는 useInfiniteQuery를 통한 Query 인스턴스는 캐시된 데이터를 stale한 것으로 간주합니다.
stale한 query는 다음과 같은 경우 백그라운드에서 자동으로 다시 가져옵니다.
-
쿼리 마운트의 새 인스턴스
-
window가 refocused 됬을 때
-
네트워크가 재연결 되었을 때
예상하지 못한 refetch가 되는 경우 refetchOnWindowFocus를 수행하기 때문일 수 있습니다.
개발 중에는 특히 브라우저 DevTools와 앱 사이에 초점을 맞추면 가져오기가 발생하므로 이 작업이 더 자주 트리거될 수 있으므로 이 점에 유의하세요.