탄탄대로
[vue-query] 추가 사항 본문
useQuery Option - enabled
TanStack Query(React Query와 같은)의 enabled 옵션을 사용하면 쿼리를 비활성화하거나 일시 중지할 수 있습니다. 이 옵션을 사용하면 특정 조건이 충족될 때까지 쿼리를 실행하지 않도록 설정할 수 있습니다. 다음은 이 옵션을 사용하는 방법과 주요 시나리오에 대한 설명입니다.
주요 포인트
- 영구적 비활성화 및 일시 중지:
- enabled 옵션은 쿼리를 영구적으로 비활성화할 수 있을 뿐만 아니라 나중에 특정 조건이 충족될 때까지 쿼리를 일시 중지할 수도 있습니다.
- 조건부 실행:
- 쿼리를 초기화할 때 바로 실행되지 않도록 하고, 특정 조건이 충족되었을 때만 실행되게 할 수 있습니다.
사용 예시
필터 폼에서의 사용
사용자가 필터 값을 입력한 후에만 쿼리를 실행하고 싶은 경우:
const [filter, setFilter] = useState('');
const { data, refetch } = useQuery(['search', filter], fetchSearchResults, {
enabled: !!filter // filter 값이 있을 때만 쿼리를 활성화
});
// 필터 값이 변경되거나 사용자가 검색 버튼을 클릭할 때 호출
const handleSearch = () => {
if (filter) {
refetch(); // refetch를 호출하여 쿼리를 수동으로 실행
}
};
사용 예시 설명
- enabled: !!filter: filter 값이 있을 때만 쿼리가 활성화됩니다. 초기에는 filter 값이 없으므로 쿼리가 실행되지 않습니다.
- refetch: 필터 값이 설정된 후 사용자가 검색 버튼을 클릭하면 쿼리를 수동으로 실행합니다.
비활성화된 쿼리의 동작
- 캐시된 데이터 초기화:
- 캐시된 데이터가 있는 경우 status: 'success' 또는 isSuccess: true 상태로 초기화됩니다.
- 캐시된 데이터가 없는 경우 status: 'pending' 및 fetchStatus: 'idle' 상태로 시작합니다.
- 자동 재조회 없음:
- 쿼리는 마운트 시 자동으로 데이터를 가져오지 않습니다.
- 쿼리는 백그라운드에서 자동으로 다시 가져오지 않습니다.
- 쿼리는 일반적으로 쿼리 새로 고침을 유발하는 query client invalidateQueries 및 refetchQueries 호출을 무시합니다.
- 수동으로 가져오기:
- refetch를 사용하여 쿼리 데이터를 수동으로 가져올 수 있습니다. 하지만 skipToken과 함께 사용하면 작동하지 않습니다.
요약
enabled 옵션을 사용하면 쿼리를 효율적으로 제어할 수 있습니다. 이를 통해 초기화 시 쿼리를 실행하지 않거나, 특정 조건이 충족될 때만 쿼리를 실행할 수 있습니다. 특히 사용자 입력이 필요하거나 특정 이벤트가 발생할 때만 데이터를 가져오고 싶을 때 유용합니다.
Paginated Queries
페이지네이션된 데이터를 렌더링하는 것은 매우 일반적인 UI 패턴이며, TanStack Query에서는 쿼리 키에 페이지 정보를 포함함으로써 이 작업이 "그냥 작동합니다."
const result = useQuery({
queryKey: ['projects', page],
queryFn: fetchProjects,
})
useQuery를 사용하면 기술적으로는 여전히 잘 작동하지만, 각 페이지 또는 커서마다 새로운 쿼리가 생성되고 소멸되기 때문에 UI가 성공 및 대기 상태로 계속 변동됩니다.
TanStack Query에서 placeholderData를 이전 데이터로 설정하거나 TanStack Query에서 내보낸 keepPreviousData 함수로 설정하면 아래와 같은 새로운 기능을 얻을 수 있습니다.
- 새 데이터가 요청되는 동안에도 마지막 성공적인 가져오기에서 데이터를 사용할 수 있습니다. 이는 쿼리 키가 변경되었더라도 해당합니다.
- 새 데이터가 도착하면 이전 데이터가 신규 데이터로 부드럽게 전환됩니다.
- isPlaceholderData를 사용하여 현재 쿼리가 제공하는 데이터를 알 수 있습니다.
placeholderData
- useQuery 옵션 중 하나입니다.
- 만약 설정된다면, 이 값은 해당 쿼리 옵저버가 대기 상태에 있을 때 이 특정 쿼리에 대한 플레이스홀더 데이터로 사용됩니다.
- placeholderData는 캐시에 저장되지 않습니다.
- placeholderData에 함수를 제공하면, 첫 번째 인자로 이전에 감시한 쿼리 데이터를 받게 됩니다(사용 가능한 경우), 두 번째 인자는 완전한 이전 쿼리 인스턴스가 될 것입니다.
const { data, isPlaceholderData } = useQuery({
queryKey,
queryFn,
placeholderData: (previousData, previousQuery) => previousData,
});
keepPreviousData
- vue-query에서 제공하는 옵션 중 하나입니다.
- 이 옵션을 사용하면 쿼리가 아직 대기 중인 상태일 때 해당 쿼리 옵저버에 대한 placeholder 데이터로 이전 데이터를 유지할 수 있습니다.
- keepPreviousData는 캐시에 저장되지 않습니다.
- 전 데이터를 사용하여 새로운 데이터를 요청할 때까지 이전 데이터가 화면에 표시됩니다.
<script setup lang="ts">
import { ref, Ref } from 'vue'
import { useQuery, keepPreviousData } from '@tanstack/vue-query'
// 페이지를 기반으로 데이터를 가져오는 함수입니다.
const fetcher = (page) =>
fetch(
`https://jsonplaceholder.typicode.com/posts?_page=${page.value}&_limit=10`,
).then((response) => response.json())
// 현재 페이지를 나타내는 ref입니다.
const page = ref(1)
// useQuery 훅을 사용하여 쿼리를 설정합니다.
const { isPending, isError, data, error, isFetching, isPlaceholderData } =
useQuery({
// 쿼리 키는 'projects'와 현재 페이지를 포함합니다.
queryKey: ['projects', page],
// 데이터를 가져오는 함수를 설정합니다.
queryFn: () => fetcher(page),
// placeholderData 옵션을 사용하여 이전 데이터를 유지합니다.
placeholderData: keepPreviousData
/*
또는 아래와 같이 선언
placeholderData: (previousData, previousQuery) => previousData
*/
})
// 이전 페이지로 이동하는 함수입니다.
const prevPage = () => {
page.value = Math.max(page.value - 1, 1)
}
// 다음 페이지로 이동하는 함수입니다.
const nextPage = () => {
// isPlaceholderData가 false인 경우에만 페이지를 증가시킵니다.
if (!isPlaceholderData.value) {
page.value = page.value + 1
}
}
</script>
<template>
<!-- 현재 페이지와 placeholderData를 표시합니다. -->
<h1>Posts</h1>
<p>Current Page: {{ page }} | Previous data: {{ isPlaceholderData }}</p>
<!-- 이전 페이지로 이동하는 버튼입니다. -->
<button @click="prevPage">Prev Page</button>
<!-- 다음 페이지로 이동하는 버튼입니다. -->
<button @click="nextPage">Next Page</button>
<!-- 데이터를 로드하는 동안에는 로딩 메시지를 표시합니다. -->
<div v-if="isPending">Loading...</div>
<!-- 에러가 발생한 경우 에러 메시지를 표시합니다. -->
<div v-else-if="isError">An error has occurred: {{ error }}</div>
<!-- 데이터가 있을 경우 데이터를 렌더링합니다. -->
<div v-else-if="data">
<ul>
<li v-for="item in data" :key="item.id">
{{ item.title }}
</li>
</ul>
</div>
</template>
코드는 다음과 같은 동작을 수행합니다.
- useQuery 훅을 사용하여 쿼리를 설정하고, 페이지를 포함한 쿼리 키를 정의합니다. placeholderData 옵션을 사용하여 이전 데이터를 유지합니다.
- prevPage 함수는 이전 페이지로 이동하고, nextPage 함수는 다음 페이지로 이동합니다. 이전 데이터가 로드되는 동안에는 페이지 이동이 제한됩니다.
useMutation 성공 후 처리 방법
쿼리 무효화
일반적으로 응용 프로그램에서 Mutation이 성공하면, Mutation로 인한 새로운 변경 사항을 반영하기 위해 쿼리를 다시 가져와야 합니다. 해당 Mutation과 관련된 쿼리를 무효화함으로 쿼리를 다시 가져올 수 있습니다.
import { useMutation, useQueryClient } from '@tanstack/vue-query'
const queryClient = useQueryClient()
// 이 Mutation이 성공하면 todos 또는 reminders 쿼리 키를 가진 모든 쿼리를 무효화합니다.
const mutation = useMutation({
mutationFn: addTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
queryClient.invalidateQueries({ queryKey: ['reminders'] })
},
})
기존 쿼리 데이터 업데이트
서버에서 객체를 업데이트하는 Mutation을 다룰 때, Mutation의 응답에 새로운 객체가 자동으로 반환되는 것이 일반적입니다.
해당 항목에 대한 쿼리를 다시 가져와서 이미 가지고 있는 데이터에 대한 네트워크 호출을 낭비하는 대신, Mutation 함수에서 반환된 객체를 활용하여 기존 쿼리를 즉시 새 데이터로 업데이트할 수 있습니다. 이를 위해 Query Client의 setQueryData 메서드를 사용할 수 있습니다.
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: editTodo,
onSuccess: (data) => {
queryClient.setQueryData(['todo', { id: 5 }], data)
},
})
mutation.mutate({
id: 5,
name: 'Do the laundry',
})
// 다음 쿼리는 성공한 Mutation의 응답으로 업데이트됩니다.
const { status, data, error } = useQuery({
queryKey: ['todo', { id: 5 }],
queryFn: fetchTodoById,
})
setQueryData
이 메서드를 사용하면 기존의 쿼리 결과를 직접적으로 변경하지 않고도 쿼리를 업데이트할 수 있습니다. 이것은 Vue Query에서 불변성을 유지하는 데 도움이 됩니다.
queryClient.setQueryData('todos', updatedData);
- 첫 번째 인자: 업데이트할 쿼리의 키
- 두 번째 인자: 해당 쿼리에 적용할 새로운 데이터
참고 공식 문서
https://tanstack.com/query/latest/docs/framework/vue/guides/invalidations-from-mutations
Invalidations from Mutations | TanStack Query Vue Docs
Invalidating queries is only half the battle. Knowing when to invalidate them is the other half. Usually when a mutation in your app succeeds, it's VERY likely that there are related queries in your application that need to be invalidated and possibly refe
tanstack.com
https://tanstack.com/query/latest/docs/framework/vue/guides/updates-from-mutation-responses
Updates from Mutation Responses | TanStack Query Vue Docs
When dealing with mutations that update objects on the server, it's common for the new object to be automatically returned in the response of the mutation. Instead of refetching any queries for that item and wasting a network call for data we already have,
tanstack.com
Infinite Queries
https://tanstack.com/query/latest/docs/framework/vue/guides/infinite-queries
Infinite Queries | TanStack Query Vue Docs
Rendering lists that can additively "load more" data onto an existing set of data or "infinite scroll" is also a very common UI pattern. TanStack Query supports a useful version of useQuery called useInfiniteQuery for querying these types of lists. When us
tanstack.com
https://github.com/ssi02014/react-query-tutorial?tab=readme-ov-file#infinite-queries
GitHub - ssi02014/react-query-tutorial: 😃 TanStack Query(aka. react query) 에서 자주 사용되는 개념 정리
😃 TanStack Query(aka. react query) 에서 자주 사용되는 개념 정리 - GitHub - ssi02014/react-query-tutorial: 😃 TanStack Query(aka. react query) 에서 자주 사용되는 개념 정리
github.com
Initial Data from Cache
다른 쿼리의 캐시된 결과에서 쿼리의 초기 데이터를 제공할 수 있습니다.
좋은 예로는 todos 리스트 쿼리의 캐시된 데이터에서 개별 todo 항목을 검색한 다음, 이를 개별 todo 쿼리의 초기 데이터로 사용하는 경우가 있습니다:
- useQuery({ queryKey: ['todos'], queryFn: fetchTodos }) 쿼리가 실행되어 todos 리스트가 캐시됩니다.
- 이후 useQuery({ queryKey: ['todo', todoId], queryFn: fetchTodoById }) 쿼리를 실행할 때, 초기 데이터로 todos 리스트 캐시에서 해당 todoId에 맞는 항목을 검색합니다.
- 이렇게 하면 네트워크 요청이 완료되기 전에 개별 todo 항목을 미리 표시할 수 있습니다.
이를 구현하기 위한 예제 코드는 다음과 같습니다:
// todos 리스트 쿼리
const { data: todos } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos
});
// 특정 todo 항목을 찾기 위한 쿼리
const todoId = 1; // 예시 todo ID
const initialTodo = todos?.find(todo => todo.id === todoId);
const { data: todo } = useQuery({
queryKey: ['todo', todoId],
queryFn: () => fetchTodoById(todoId),
initialData: initialTodo
});
이 방식으로 캐시된 데이터를 활용하면 사용자가 데이터를 더 빠르게 확인할 수 있어, 사용자 경험이 향상됩니다. 네트워크 요청이 완료되면 최신 데이터로 자동 업데이트되므로 데이터의 신뢰성도 유지할 수 있습니다.
쿼리 데이터 업데이트 시간 설정
캐시에서 초기 데이터를 얻는다는 것은 초기 데이터를 조회하는 소스 쿼리가 오래되었을 가능성이 있다는 것을 의미합니다. 쿼리가 데이터를 즉시 다시 가져오지 않도록 인위적인 staleTime을 사용하는 대신, 소스 쿼리의 dataUpdatedAt을 initialDataUpdatedAt으로 전달하는 것이 좋습니다.
이렇게 하면 초기 데이터가 제공되었는지 여부와 관계없이 쿼리 인스턴스가 쿼리가 다시 가져와야 하는지 여부를 판단하는 데 필요한 모든 정보를 갖추게 됩니다.
다음은 이를 구현하는 예제입니다:
// todos 리스트 쿼리
const { data: todos, dataUpdatedAt: todosUpdatedAt } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos
});
// 특정 todo 항목을 찾기 위한 쿼리
const todoId = 1; // 예시 todo ID
const initialTodo = todos?.find(todo => todo.id === todoId);
const { data: todo } = useQuery({
queryKey: ['todo', todoId],
queryFn: () => fetchTodoById(todoId),
initialData: initialTodo,
initialDataUpdatedAt: todosUpdatedAt
});
위 코드에서 initialDataUpdatedAt을 설정하면, 쿼리가 초기 데이터를 사용하더라도 이 데이터가 언제 업데이트되었는지를 알 수 있습니다.
이렇게 하면 쿼리가 초기 데이터의 최신성을 판단하고 필요한 경우 다시 가져올 수 있습니다.
'FrontEnd > Vue.js' 카테고리의 다른 글
[vue-query] 개요 (0) | 2024.06.19 |
---|---|
[용어] 스캐폴딩 (0) | 2024.03.21 |