캐시스토어는 개발자가 직접 만드는 전역 상태 저장소로, 구현 방식은 크게 3가지.
1. 메모리 저장 방식
2. 브라우저 저장 방식
3. 인메모리 캐시와 브라우저 저장소 혼합 방식
캐시스토어는 애플리케이션의 어디서나 접근 가능해야 하므로, 보통 다음 위치 중 하나에 구현한다.
(탠스택 쿼리는 이런 복잡한 캐시 관리를 자동화, 직접 구현할 필요가 없게 해준다.)
React + 일반 상태관리
🖥️ javascript
// 캐시를 위한 전역 상태 관리
const cacheStore = {
todos: {
data: null,
lastFetched: null,
isLoading: false,
error: null
}
};
function TodoList() {
const [todos, setTodos] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchTodos = async () => {
// 캐시가 있고 5분이 지나지 않았다면 캐시 사용
if (cacheStore.todos.data &&
Date.now() - cacheStore.todos.lastFetched < 300000) {
setTodos(cacheStore.todos.data);
return;
}
setIsLoading(true);
try {
const response = await axios.get('/todos');
const data = response.data;
// 캐시 저장
cacheStore.todos.data = data;
cacheStore.todos.lastFetched = Date.now();
setTodos(data);
} catch (err) {
setError(err);
cacheStore.todos.error = err;
} finally {
setIsLoading(false);
cacheStore.todos.isLoading = false;
}
};
fetchTodos();
}, []);
// 수동으로 모든 에러 상태 처리
if (error) return <div>Error: {error.message}</div>;
if (isLoading) return <div>Loading...</div>;
if (!todos) return null;
return (/* JSX */);
}
QueryCache를 사용. 쿼리캐시는 Tanstack Query가 기본적으로 메모리 내에 가지고 있는 내부 캐시 저장소로, QueryClient 인스턴스를 통해 관리된다.
기본 캐시 설정
🖥️ javascript
// QueryClient 생성 시 캐시 설정 가능
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5000,
cacheTime: 300000,
}
}
});
캐시 구조
🖥️javascript
// 내부적으로 이런 형태로 저장됨
{
queries: {
['todos']: {
data: [...],
dataUpdateCount: 1,
dataUpdatedAt: 1642512948299,
error: null,
errorUpdateCount: 0,
errorUpdatedAt: 0,
fetchStatus: 'idle',
status: 'success'
}
}
}
캐시 조작 방법
🖥️ javascript
// 캐시 직접 접근
const cachedData = queryClient.getQueryData(['todos'])
// 캐시 수동 설정
queryClient.setQueryData(['todos'], todos)
// 캐시 무효화
queryClient.invalidateQueries(['todos'])
// 캐시 삭제
queryClient.removeQueries(['todos'])
➡️ 별도의 캐시스토어를 만들 필요 없이 QueryClient가 모든 캐시 관리를 담당한다. 브라우저를 새로고침하면 메모리 캐시는 초기화되는데, 필요한 경우 localStorage나 다른 영구 저장소와 연동할 수도 있다.
🖥️ javascriptC
function TodoList() {
const { data: todos, isLoading, error } = useQuery({
queryKey: ['todos'],
queryFn: () => axios.get('/todos').then(res => res.data),
staleTime: 300000 // 5분
});
if (error) return <div>Error: {error.message}</div>;
if (isLoading) return <div>Loading...</div>;
return (/* JSX */);
}
캐싱 자동화
에러 처리 간소화
자동 동기화
❓mutation 후 관련 쿼리 자동 무효화? (useMutation은 자동 캐싱X)
➡️ mutation 후 관련 쿼리 자동 무효화와 자동 캐싱은 다른 개념이다.
useMutation은 자동 캐싱X
: mutation은 데이터를 변경하는 작업이므로, 매번 새로운 요청 필요.
따라서 결과를 캐시에 저장하지 않음
mutation 후 관련 쿼리 무효화
🖥️ javascript
const mutation = useMutation({
mutationFn: addTodo,
onSuccess: () => {
// 여기서 관련 쿼리를 수동으로 무효화
queryClient.invalidateQueries({ queryKey: ['todos'] });
}
});
❗️useMutation은 자동으로 쿼리를 무효화하지 않습니다.
개발자가 onSuccess 콜백에서 queryClient.invalidateQueries()를 호출하여 수동으로 무효화 처리를 해야 합니다. mutation 성공 후 관련 쿼리의 캐시를 수동으로 무효화하면, 그 때 해당 쿼리가 자동으로 리페칭됩니다.
캐시 제어 옵션
🖥️ javascript
const { data } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
staleTime: 5000, // 5초 동안 신선한 상태 유지
cacheTime: 300000, // 5분 동안 캐시 유지
refetchOnMount: false, // 마운트 시 재요청 비활성화
refetchOnWindowFocus: false // 윈도우 포커스 시 재요청 비활성화
});
복잡한 데이터 캐싱과 상태 관리 로직을 추상화, 비즈니스 로직에 집중 가능.
특히 실시간성이 중요한 애플리케이션에서 데이터의 일관성을 유지하면서도
성능을 최적화하는 데 도움.