React Query는 데이터 Fetching, 캐싱, 동기화, 서버 쪽 데이터 업데이트 등을 쉽게 만들어 주는 React 라이브러리이다.
React-Query가 만들어진 이유
이 점을 해결하기 위해 React-Query가 만들어졌다.
React-Query의 장점
✔️ 위에서 설명한 것처럼 React 어플리케이션 내에서 데이터 패칭, 캐싱, 동기적, 그리고 서버의 상태의 업데이트를 좀 더 용이하기 위해 만들어준다.
✔️ 기존에는 직접 만들어서 사용했던 기능들을 별도의 옵션으로 지원하여 복잡하고 이해할 수 없는 수많은 코드를 대신 React-Query 로직을 통해 짧은 코드로 대체할 수 있게 되었다.
✔️ 프로젝트 구조가 기존보다 단순해져 애플리케이션을 유지 보수하기 쉽고, 새로운 기능을 쉽게 구축할 수 있다.
그 외에도 많은 장점이 있다.
→ React Query는 별도의 설정 없이 즉시 사용 가능하다
→ 캐싱을 효율적으로 관리해준다
→ 같은 데이터에 대한 여러번의 요청이 있을 시 중복을 제거한다.
→ 백그라운드에서 알아서 오래된 데이터를 업데이트해준다.
→ 데이터 업데이트 시 최대한 빨리 반영한다.
→ 페이징처리, 지연 로딩 데이터와 같은 성능 최적화해준다
→ 서버 쪽 데이터를 가비지 컬렉션을 이용하여 자동으로 메모리를 관리해준다.
→ 구조적 공유를 통해 쿼리의 결과를 기억해준다.
사용예시
import { useQuery } from 'react-query'
function App() {
const info = useQuery('todos', fetchTodoList)
}
Query는 GET, POST 메소드 포함을 기반으로 서버에서 데이터를 패칭하기 위한 비동기 함수와 사용된다.
'todos': 첫번째 인자로, unique key를 명시해준다.
✔️ 해당 key는 내부적으로 데이터 재요청, 캐싱, 쿼리를 공유하기 위해 사용된다.
fetchTodoList: 두번째 인자에는 우리가 요청할 비동기 함수를 넣어주는데, 데이터와 error를 return해준다.
useQuery return값에 포함되어 있는 states
주요 states
사용방법
function Todos() {
const { isLoading, isError, data, error } = useQuery('todos', fetchTodoList)
if (isLoading) {
return <span>Loading...</span>
}
if (isError) {
return <span>Error: {error.message}</span>
}
// We can assume by this point that `isSuccess === true`
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
방법 2. status 하나로 꺼내서 사용할 수도 있음
function Todos() {
const { status, data, error } = useQuery('todos', fetchTodoList)
if (status === 'loading') {
return <span>Loading...</span>
}
if (status === 'error') {
return <span>Error: {error.message}</span>
}
// also status === 'success', but "else" logic works, too
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
Query Keys
✅ React Query는 query key로 캐싱을 관리한다.
✅ 간단한 문자열이어도 되고, 배열 형태, 중첩된 객체와 같이 복잡하든 상관없다
✅ Query key가 순차적 진행을 보장하는 직렬화 기법으로 쿼리의 데이터는 유일하다.
✔️ Query key가 문자열일 때
// A list of todos
useQuery('todos', ...) // queryKey === ['todos']
// Something else, whatever!
useQuery('somethingSpecial', ...) // queryKey === ['somethingSpecial']
✔️ Query key가 배열일 때
// An individual todo
useQuery(['todo', 5], ...)
// queryKey === ['todo', 5]
// An individual todo in a "preview" format
useQuery(['todo', 5, { preview: true }], ...)
// queryKey === ['todo', 5, { preview: true }]
// A list of todos that are "done"
useQuery(['todos', { type: 'done' }], ...)
// queryKey === ['todos', { type: 'done' }]
✔️ Query key에 있는 object들의 순서는 중요하지 않다.
동일한 Query key -> array의 object내 에 있는 값들의 순서는 중요하지 않음 (동일함)
useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)
동일 하지 않은 Query key. -> array 값의 순서는 중요하다
useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)
✔️ Query key는 파라미터를 사용해도 된다.
파라미터가 unique한 값이라면 인자로 받아 아래와 같이 사용할 수 있다.
function Todos({ todoId }) {
const result = useQuery(['todos', todoId], () => fetchTodoById(todoId))
}
✅ Query Function은 promise를 return하는 함수
✅ Promise는 data를 return하거나 에러가 나면 에러를 return한다.
아래와 같이 function을 작성할 수 있다.
useQuery(['todos'], fetchAllTodos)
useQuery(['todos', todoId], () => fetchTodoById(todoId))
useQuery(['todos', todoId], async () => {
const data = await fetchTodoById(todoId)
return data
})
useQuery(['todos', todoId], ({ queryKey }) => fetchTodoById(queryKey[1]))
✅ Query Function에서 발생된 에러는 query의 error state에 남는다.
const { error } = useQuery(['todos', todoId], async () => {
if (somethingGoesWrong) {
throw new Error('Oh no!')
}
return data
})
✅ fetch와 같이 기본적으로 Http 요청 실패에 대한 에러를 제공하지 않는 경우 아래와 같이 직접 error를 던져줘야한다.
✔️ axios와 graphql-request과 같은 대부분의 함수가 기본적으로 HTTP 요청이 실패하면 에러를 반환하지만,
fetch와 같이 몇몇 함수는 오직 network error에 대해서만 에러를 내뱉는 경우가 있다.
이 경우에 아래와 같은 방법으로 에러를 직접 반환해줘야한다.
useQuery(['todos', todoId], async () => {
const response = await fetch('/todos/' + todoId)
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
})
✅ Query Function Variables
Query keys는 데이터를 요청하는동안 식별한는 것 뿐만아니라 또한 필요할 때 편리하게 Query Function에서 사용도 할 수 있다.
이렇게 하면, 필요할 때마다 아래와 같이 Query Function 에서 추출해서 사용할 수 있다.
function Todos({ status, page }) {
const result = useQuery(['todos', { status, page }], fetchTodoList)
}
// Access the key, status and page variables in your query function!
function fetchTodoList({ queryKey }) {
const [_key, { status, page }] = queryKey
return new Promise()
}
즉 _key는 'todos' 즉 식별자를 개발자가 확실하게 키값으로 인지하기위해서 편하게 넣은 값 나머지는 비구조화 할당을 통해서 데이터 가공이 가능하다.
📍 Dependent Queries: 동기적으로 사용하기
✅ enabled: true이면 특정 조건이 trued 때만 실행하겠다
→ 이전 함수가 끝나고 다음 것이 영향을 받을 때 enabled option을 사용하면 좋다.
const { data: user } = useQuery(['user', email], getUserByEmail)
const userId = user?.id
// Then get the user's projects
const { isIdle, data: projects } = useQuery(
['projects', userId],
getProjectsByUser,
{
// The query will not execute until the userId exists
enabled: !!userId,
}
)
📍 Loading state 전역으로 사용하기
✅ useIsFetching
→ useQuery 내부 isLoading state 말고 backgroud에서의 fetching 체크가능하다
import { useIsFetching } from 'react-query'
function GlobalLoadingIndicator() {
const isFetching = useIsFetching()
return isFetching ? (
<div>Queries are fetching in the background...</div>
) : null
}