전체적인 변화
매개변수를 객체 하나로 통일
// v4
useQuery(key, fn, options);
useInfiniteQuery(key, fn, options);
useMutation(fn, options);
// ...
// v5
useQuery({ queryKey, queryFn, ...options });
useInfiniteQuery({ queryKey, queryFn, ...options });
useMutation({ mutationFn, ...options });
// ...
useQuery
onSuccess, onError, onSettled 제거
https://tkdodo.eu/blog/breaking-react-querys-api-on-purpose 인상 깊었던 부분 일부 번역
추가 렌더링 사이클
많은 사람들이 상태를 동기화하는 데, 해당 콜백을 쓰곤 합니다. 제발 이렇게 하지마세요!
export function useTodos() {
const [todoCount, setTodoCount] = React.useState(0);
const { data: todos } = useQuery({
queryKey: ["todos", "list"],
queryFn: fetchTodos,
//😭 please don't
onSuccess: (data) => {
setTodoCount(data.length);
},
});
return { todos, todoCount };
}
이러한 코드는 불필요한 렌더링을 유발합니다. 렌더링 과정은 다음과 같습니다.
todos
는undefined
고length
가 0일 것입니다. 이는 기본 상태값으로, 옳습니다.todos
는length
가 5인 배열이 되고,todoCount
는 0일 것입니다.useQuery
는 이미 돌았으나,setTodoCount
는 실행되지 않은 사이클에 속해있기 때문입니다. 이는 값이 동기화되지 않았으므로 잘못됐습니다.todos
는length
가 5인 배열이 되고,todoCount
는 5가 될 것입니다. 이것이 최종 상태이며 다시 옳습니다.
간단히 수정하자면, 상태를 사용하는 것 대신 계산할 수 있습니다. https://tkdodo.eu/blog/dont-over-use-state (주제 관련된 글)
export function useTodos() {
const { data: todos } = useQuery({
queryKey: ["todos", "list"],
queryFn: fetchTodos,
});
const todoCount = todos?.length ?? 0;
return { todos, todoCount };
}
이 todoCount
가 동기화되지 않을 경우는 없습니다.
그렇다면 어떻게 상태를 관리해야할까
상태 동기화가 불가피하거나, 성공 이후에 처리해야하는 로직의 경우
→ 제 생각이므로 참고만 부탁드려요
// Cursor AI가 만들어준 코드
const { data, isSuccess } = useQuery({
queryKey: ["user"],
queryFn: fetchUser,
});
useEffect(() => {
if (isSuccess && data) {
// Simple side effects
updateLastLoginTime();
showWelcomeMessage();
}
}, [isSuccess, data]);
가져온 data를 가공해 사용하는 경우(무거운 연산을 실행할 때 권장)
// Cursor AI가 만들어준 코드
const { data } = useQuery({
queryKey: ["analytics"],
queryFn: fetchAnalytics,
select: (data) => {
// Complex data reshaping/aggregation
return data.map((item) => ({
...item,
metrics: processMetrics(item.rawData),
trends: calculateTrends(item.historicalData),
}));
},
});
remove 제거
앞으로는 remove
대신 이러한 방식으로 사용
const queryClient = useQueryClient();
const query = useQuery({ queryKey, queryFn });
queryClient.removeQueries({ queryKey });
cacheTime
gcTime
으로 이름 변경
useInfiniteQuery
initialPageParam
이 필수값으로 추가됨queryFn
의 pageParam의 기본값으로 설정됨
그 외에도 많은 변화가 있으나, 작업하며 느낀 큰 변화를 중점으로 작성해보았습니다.
현재 고민인 부분
-
성공 시(
onSuccess
)에 같은 처리를 하는 같은 Query의 경우 useEffect에 dependency 걸어서 데이터 처리 시, 각각의 컴포넌트에 중복 코드가 발생하는데 이걸 어떻게 처리하면 좋을지.. 커스텀 훅이 좋을지 애초에 더 좋은 방법이 있을지 고민 -
remove 제거할 때 UI단 코드에서 아래 코드를 사용하게 되는데, 이게 과연 최선일지
queryClient.removeQueries({ queryKey });
-> 해결하게 되면 또 새로운 글로 올릴 예정!