react query cache를 사용하자

Dami·2023년 3월 9일
0

react-query

목록 보기
2/2
post-thumbnail

react query는 캐시 관련 옵션들이 존재한다. 오늘은 캐시를 사용해서 최적화를 해보는 시간을 가져보겠다.

먼저, 캐시를 사용하는 이유와 장단점을 알아보자.

- 캐시를 사용하는 이유

api를 호출하는데 데이터 변경이 짧은 시간에 이루어지는게 아니라 시간, 일 이렇게 변경 주기가 큰 api들이 있다. 즉, 1시간 동안 100번을 호출한다고 해도 똑같은 정보가 온다는 것이다. 이런 경우 불필요하게 매번 호출하는 것 보다는 한 번 호출해서 캐시에 저장하고 일정 시간 동안은 캐시 데이터를 가져와서 출력해주는게 프로젝트에 부담도 덜하고 api 과부하 방지에도 좋기 때문에 캐시를 사용한다.

- 캐시 사용 장점

  • 서버, 프로젝트 과부하 방지 ( 사용자 경험 좋아짐 )
  • 이미 가지고 있는 데이터라 로딩 X

- 캐시 사용 단점

  • 데이터의 무결성을 해치는 위험성이 존재

캐시는 잘 사용하면 정말 좋지만 잘못쓰면 이상한 데이터를 보여주는 쓰레기가 된다.
그러니까 사용해야 할 곳을 정말정말 잘 판단해서 사용을 해야겠다.

이제 react query로 캐시를 사용해보자

본인은 useQuery를 커스텀 hooks로 따로 만들어서 거쳐서 사용을 하고 있다.
커스텀 hooks는 이런 형태다.

const useQuery = <T>(parameter: QueryParameter<T>) => {
	const {
		url,
		isActive,
		refreshInterval,
		onSuccessCache,
		onSuccess,
		onSettled,
		onError,
	} = parameter;
    
	const [currentUser] = ...;
	const API: AxiosInstance = ...;
	const interval = ...;
	const active = ...;
    
	const {
		data,
		isLoading,
		error,
		refetch,
		isInitialLoading,
		isFetching,
	}: QueryResult<T> = query({
		queryKey: [`${url}`, currentUser],
		queryFn: () => getAPI(url, API),
		enabled: active,
		refetchOnWindowFocus: false,
		refetchInterval: interval,
		onSuccess,
		onSettled,
		onError,
	});

	return {
		data,
		isLoading: isLoading && isInitialLoading,
		refetch,
		error,
		isFetching,
	};
};

여기에 이제 interface랑 query에 staleTime, cacheTime를 추가해서 사용을 하면 된다.

staleTime에 300000ms 를 줘서 동작하는 것을 확인 했다. 그런데 예상하지 못한 문제가 있었다.

- 문제

api를 사용하다보면 꼭 setState 저장을 해서 사용해야 하는경우가 있는데 이럴 때 유용하게 사용했던게 콜백함수다. onSuccess 콜백함수를 이런식으로 사용을 자주 했다.

const { data: list = [] } = useQuery<APIUrlsProps['getList'][]>({
	url: APIUrls.getList(),
	onSuccess: data => setList(data),
});

이렇게 사용하면 api 호출에 성공하면 state에 값이 담기게 된다. 하지만, 캐시 데이터를 가져오는 경우 api 호출 성공이 아니라서 onSuccess 콜백함수가 동작하지 않는다. 이렇게되면 매번 따로 데이터를 저장하는 로직을 넣어줘야 한다.

- 문제 해결

여러가지를 방법을 생각해보다가 커스텀 hooks 내부에서 데이터가 호출될 때 함수를 실행해서 콜백해주면 되겠다고 생각이 들어서 내부에 useEffect를 사용해서 넣어주었다.

const {
	data,
	isLoading,
	error,
	refetch,
	isInitialLoading,
	isFetching,
}: QueryResult<T> = query({
	...
});

useEffect(() => {
	if (!!onSuccessCache && !!staleTime && !(isLoading || isInitialLoading)) {
		onSuccessCache(data);
	}
}, [
	staleTime,
	onSuccess,
	data,
	isLoading,
	isInitialLoading,
	onSuccessCache,
]);

onSuccessCache 콜백 함수 옵션을 받아서 staleTime 옵션이 있으면 캐시를 사용하는 것으로 간주하고 userQuery가 뱉는 데이터가 로딩중이 아닐 때 데이터를 콜백하게 만들어서 해결했다.

+ 수정

문제 해결을 위해 콜백함수를 추가해서 동작하게 했는데 실무에서 사용했더니 무한 리렌더링이 발생했다. 이유는 useEffect 에서 setState가 실행되면서 렌더링을 유발한 것 같다.

그래서 이를 해결하려고 이전 데이터와 현재 데이터를 비교해서 다르거나 비어 있으면 콜백 함수를 호출하는 로직으로 변경을 했다.

/** oldData, newData가 다른 값을 가지고 있는지 확인 */
const createDataEqualityChecker = useCallback(
	(oldData: T) =>
		(newData: T): boolean =>
			isEqual(oldData, newData),
	[],
);

유틸을 만들어 이전 데이터와 현재 데이터를 비교한다

useEffect(() => {
	if (isSuccess && !isFetching && onSuccessCached) {
		const isDataEqual = createDataEqualityChecker(oldData);
		if (!isDataEqual(data)) {
			onSuccessCached(data);
			setOldData(data);
		}
	}
}, [
	isFetching,
	isSuccess,
	onSuccessCached,
	createDataEqualityChecker,
	data,
	oldData,
]);

서로 다른 데이터일 때만 onSuccessCached 호출하고 이전 데이터 업데이트를 한다.
페이지 이동시 => oldData는 undefined로 초기화 하고 데이터를 한번 가져옴
데이터 리패치 => 리패치할 때 oldData를 undefined로 초기화 ...

아무튼 실무에서 사용하기위해 이렇게 세팅했다.

- 결론

const { data: list = [] } = useQuery<APIUrlsProps['getList'][]>({
	url: APIUrls.getList(),
	onSuccessCache: data => setList(data),
    staleTime: (60 * 5 * 1000), //default 0
	cacheTime: (60 * 6 * 1000) //default 300000
});

캐시 사용은 장단점이 확실해서 유용하면서도 위험하다는게 나의 결론이다.
그리고 캐시 데이터를 가져오면 콜백함수가 동작을 안한다는 것을 react query 사용자라면 알아둘 필요가 있다.

profile
주니어 개발자 다미

0개의 댓글