React Query - 가이드 및 개념 (3)
React Query - 가이드 및 개념 정리
포스팅의 내용은 모두 React Query Docs의 내용과 제 사견으로 이루어져 있습니다. 이해하기에 부족하거나 빠진 내용이 있을 수 있을 수 있으므로 더 자세한 내용을 확인하시고 싶으시다면 공식 Doc 을 참고해 주시면 감사하겠습니다.
기본적으로 React Query 는 공격적이지만 정상적인 기본값(default) 으로 구성됩니다.
때때로 이러한 기본값은 새로운 사용자를 경계하거나 사용자가 모르는 경우 학습 및 디버깅을 어렵게 만들 수 있습니다. React Query를 배우고 사용할 때 이 부분을 항상 명심하세요.
useQuery 또는 useInfinite 를 통해 쿼리 인스턴스는 기본적으로 캐시된 데이터를 오래된 것으로 간주합니다.
이 동작을 변경하려면 staleTime
옵션을 사용하여 쿼리를 전역적으로 또는 쿼리별로 구성할 수 있습니다. 더 긴 staleTime
을 지정하면 쿼리가 데이터를 자주 다시 가져 오지 않습니다.
오래된 쿼리는 다음과 같은 경우 백그라운드에서 자동으로 다시 가져옵니다.
쿼리 마운트의 새 인스턴스
- window 가 변경될 때(refocused)
- 네트워크가 다시 연결될 때
- 쿼리는 선택적으로 ‘다시 가져오기 간격’(refetch interval)으로 구성된다.
이 기능을 변경하려면 refetchOnMount, refetchOnWindowFocus, refetchOnReconnect 및 refetchInterval 과 같은 옵션을 사용할 수 있습니다.
useQuery, useInfiniteQuery 또는 쿼리 관찰자(query observers)의 활성 인스턴스가 더 이상 없는 쿼리 결과는 ‘비활성(inactive)’으로 Label이 지정되고 나중에 다시 사용할 경우 캐시에 남아 있습니다.
- 기본적으로 ‘비활성(inactive)’ 쿼리는 5분 후에 가비지 수집으로 회수됩니다.
이 기능을 변경하려면 쿼리의 기본 cacheTime 을 1000 * 60 * 5 밀리 초가 아닌 다른 값으로 변경할 수 있습니다.
실패한 쿼리는 캡쳐하여 UI에 오류를 표시하기 전에 ‘지수 백 오프 지연’(exponential backoff delay)과 함께 자동으로 3회 재 시도됩니다.
이 기능을 변경하려면 쿼리에 대한 기본 retry 및 retryDelay 옵션을 3이 아닌 다른 값과 기본 ‘지수 백 오프 함수’(exponential backoff function)로 변경할 수 있습니다.
기본적으로 쿼리 결과는 데이터가 실제로 변경되었는지 감지하기 위해 구조적으로 공유되며 그렇지 않은 경우 useMemo 및 useCallback 과 관련하여 값 안정화를 더 잘 지원하기 위해 데이터 참조가 변경되지 않은 상태로 유지됩니다. 이 개념이 잘 이해가 안되더라도 걱정하지 마세요. 99.9%의 시간동안 이 기능을 비활성화 할 필요가 없으며 비용없이 앱의 성능이 향상됩니다.
구조적 공유는 JSON-compatible(호환) 값으로만 작동하며, 다른 값 유형은 항상 변경된 것으로 간주됩니다. 예를들어, 큰 응답으로 인해 성능 문제가 발생하는 경우, config.structuralSharing 플래그를 사용하여 이 기능을 비활성화 할 수 있습니다.
쿼리 응답에서 JSON과 호환되지 않는 것을 처리하고 데이터가 변경되었는지 여부를 계속 감지하려는 경우, config.isDataEqual 을 사용하여 데이터 비교 함수를 정의할 수 있습니다.
쿼리 (Queries)
쿼리 기본사항
쿼리는 고유 Key(unique key)에 연결된 비동기 데이터 소스에 대한 선언적 종속성입니다. Promise 기반 메서드 (GET 및 POST 메서드 포함)와 함께 쿼리를 사용하여 Server에서 데이터를 가져올 수 있습니다. 메서드가 Server의 데이터를 수정하는 경우, 대신 Mutations를 사용하는 것이 좋습니다.
구성요소 또는 사용자 정의 Hooks에서 쿼리를 구독하려면 최소한 다음을 사용하여 useQuery Hook를 호출합니다.
- 쿼리에 위한 고유키 사용
- 다음과 같은 Promise를 반환하는 함수: 데이터를 확인 (Resolves), 또는 오류가 발생
import { useQuery } from 'react-query'
function App() {
const info = useQuery('todos', fetchTodoList)
}
제공 한 고유 키(unique key)는 애플리케이션 전체에서 쿼리를 다시 가져오고, 캐싱하고 공유하기 위해 내부적으로 사용됩니다.
useQuery 가 반환하는 쿼리 결과에는 템플릿 및 기타 데이터 사용에 필요한 쿼리에 대한 모든 정보가 포함되어 있습니다.
const result = useQuery('todos', fetchTodoList)
result 객체에는 생산성을 높이기 위해 알아야 할 몇가지 매우 중요한 상태가 포함되어 있습니다. 쿼리는 항상 다음 상태 중 하나 일 수 있습니다.
- isLoading 또는 status === ‘loading’ 쿼리에 데이터가 없으며 현재 가져 오는 중입니다.
- isError 또는 state === ‘error’ 쿼리에 오류가 발생했습니다.
- isSuccess 또는 status === ‘success’ 쿼리가 성공했으며 데이터를 사용할 수 있습니다.
- isIdle 또는 state === ‘idle’ 쿼리가 현재 비활성화 되어 있습니다.
이러한 기본 상태 외에도 쿼리 상태에 따라 더 많은 정보를 사용할 수 있습니다.
error
쿼리가 isError 상태이면, error 속성을 통해 error를 사용할 수 있습니다.
data
쿼리가 success 상태이면, data 속성을 통해 data를 사용할 수 있습니다.
isFetching
어떤 상태에서는 쿼리가 언제든지 가져오는 중이라면(백그라운드 다시 가져오기 포함) isFetching 이 true 가 됩니다.
대부부분의 쿼리의 경우, 일반적으로 isLoading 상태를 확인한 다음 isError 상태를 확인한 다음, 마지막으로 data를 사용할 수 있다고 가정하고 success 상태를 렌더링합니다.
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>
)
}
만약 isError 나 isLoading 같은 booleans 결과가 필요하지 않은 경우, 항상 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>
)
}
status 객체를 통해 loading, error 등의 상태를 체크하고 사용할 수 있습니다.
쿼리 키 (Query Keys)
그 중심에서 React Query는 쿼리 키를 기반으로 쿼리 캐싱을 관리합니다. 쿼리 키는 문자열처럼 간단 할 수도 있고, 여러 문자열과 중첩된 개체의 배열처럼 복잡할 수도 있습니다. 쿼리 키가 직렬화 가능하고 쿼리 데이터에 고유한 경우 이 키를 사용할 수 있습니다.
문자열 전용 쿼리 키 (String-Only Query Keys)
가장 단순한 형태의 키는 실제로 배열이 아니라 개별 문자입니다. 문자열 쿼리 키가 전달되면 쿼리 키의 유일한 항목인 문자열을 사용하여 내부적으로 배열로 변환합니다. 이 형식은 다음 경우에 유용합니다.
- 일반 목록 / 인덱스 리소스
- 비계층적 리소스
// A list of todos
useQuery('todos', ...) // queryKey === ['todos']
// Something else, whatever!
useQuery('somethingSpecial', ...) // queryKey === ['somethingSpecial']
배열 키 (Array Keys)
쿼리에서 데이터를 고유하게 설명하기 위해 더 많은 정보가 필요한 경우, 문자열 및 직렬화 가능한 개체를 포함하는 배열을 사용하여 설명할 수 있습니다. 이것은 다음과 같은 경우에 유용합니다.
// An individual todo
useQuery(['todo', 5], ...)
// queryKey === ['todo', 5]
// And 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' }]
쿼리 키는 결정적으로 해시됩니다. (hashed)
즉, 개체의 키 순서에 상관없이 다음 쿼리는 모두 동일한 것을 간주됩니다.
useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)
그러나 다음의 쿼리 키는 동일하지 않습니다. 배열 항목의 순서가 중요합니다.
useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)
쿼리 기능이 변수에 따라 달라지는 경우, 쿼리 키에 포함
쿼리 키는 가져오는 데이터를 고유하게 설명하므로 변경되는 쿼리 기능에 사용하는 모든 변수를 포함해야 한다. 예를들면,
function Todos({ todoId }) {
const result = useQuery(['todos', todoId], () => fetchTodoById(todoId))
}
todoId 의 값이 변경되면 내부적으로 변경된 쿼리 키에 맞는 데이터를 가져옵니다.
다음편에 계속..
Leave a comment