TanStack Query를 회사 실무에서 사용하면서 최근에 내가 놓쳤던 부분들에 대해 정리해보려고 한다.
변경되는 값을 쿼리 키로 쓰고 싶다면 반응형 객체를 키로 써야 한다.
예를 들어 사용자 input에 따라 payload로 api를 쏘고 응답값을 캐싱한다고 할 때
// vue.js로 작성
const input = ref('');
const { data } = useQuery(['userInput', input], () => {
fetch();
})
setTimeout(() => {
input.value = '123';
}, 1000)
이렇게 작성할 경우 먼저 ['userInput', ''] 라는 쿼리 키를 가진 캐시 데이터가 하나 생성될 것이고, 그 다음 1초 뒤에 키가 변경되면서 다시 ['userInput', '123'] 값으로 active 상태의 캐시 데이터가 생성될 것이다.
만약 아래와 같이 input.value를 키 값으로 사용해버리면 input 값의 상태 변경에 따라 키 값이 다시 세팅 되지 않을 것이다. 맨 초기값인 ''가 유일무이한 second key가 된다. 그러면 키 변경에 따른 캐시 데이터 관리가 어려워진다.
const { data } = useQuery(['userInput', input.value], () => { fetch() }) // ❌
다들 select는 한 번씩 사용해 본 경험이 많을 것이다. 서버에서 내려주는 응답이 항상 클라이언트에서 렌더링하기 위해 필요한 데이터의 모양과 일치하지는 않기 때문이다. response를 가지고 특정 value를 만들어줘야 하거나 특정 값을 주입해주어야 하는 경우 종종 사용한다. 이 때 select 메소드에 매개변수로 들어온 원본 캐시 데이터를 조작하면 어떻게 될까?
예를 들어, Array의 sort 메소드와 같은 특정 메소드들은 원본 배열 자체에 영향을 준다. 반환값 자체도 정렬된 배열이지만, 원본 객체에도 영향을 준다. 복사본을 만들지 않는다.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
function Example() {
const { isLoading, error, data, isFetching } = useQuery(
"sortArray",
() =>
new Promise((res) => {
return setTimeout(() => {
res([1, 2, 3, 4]);
}, 1000);
}),
{
select: (data) => {
return data.sort((a, b) => b - a);
}
}
);
console.log(data); // result : [4,3,2,1]
console.log(queryClient.getQueryData("sortArray")); // result: [4,3,2,1]
}
놀랍게도 답은 "원본 캐시에도 그 영향이 미친다" 이다. 즉 select로 반환한 쿼리의 값만 정렬되는 것이 아니라 원본 캐시의 값도 정렬이 되어 버린다. select의 매개변수로 들어오는 query function의 return 값은 결국 캐시된 값인데, 최종 캐시 값이 바뀌어 버리는 것이다. 그러므로 원본 객체 값을 변형시킬 수 있는 메소드를 쓸 때는 깊은 복사를 통해 캐시 값을 복사한 뒤 사용하는 것이 데이터 안정성 측면에서 더 나은 방향이라고 볼 수 있다.
fin.