🔥 React Query의 렌더링 최적화 기법

411자
6분

React Query는 컴포넌트가 실제로 필요한 경우에만 리렌더링되도록 자동으로 몇 가지 최적화를 적용합니다. 이는 다음과 같은 방법으로 이루어집니다.

구조적 공유(Structural Sharing)

React Query는 "구조적 공유"라는 기법을 사용하여 리렌더링 사이에 가능한 한 많은 참조를 그대로 유지합니다. 네트워크를 통해 데이터를 가져오는 경우 일반적으로 JSON 파싱을 통해 완전히 새로운 참조를 얻게 됩니다. 그러나 React Query는 데이터에 아무런 변화가 없으면 기존 참조를 유지합니다. 일부분만 변경된 경우에는 변경되지 않은 부분은 그대로 유지하고 변경된 부분만 교체합니다.

참고: 이 최적화는 queryFn이 JSON 호환 데이터를 반환하는 경우에만 작동합니다. 전역적으로 또는 쿼리별로 structuralSharing: false를 설정하여 이 기능을 끌 수 있습니다. 또는 함수를 전달하여 직접 구조적 공유를 구현할 수도 있습니다.

참조 동일성(Referential Identity)

useQuery, useInfiniteQuery, useMutation에서 반환되는 최상위 객체와 useQueries에서 반환되는 배열은 참조적으로 안정적이지 않습니다. 매 렌더링마다 새로운 참조가 생성됩니다. 그러나 이러한 훅에서 반환되는 데이터 속성은 가능한 한 안정적으로 유지됩니다.

추적되는 속성(Tracked Properties)

React Query는 useQuery에서 반환된 속성 중 하나가 실제로 "사용"된 경우에만 리렌더링을 트리거합니다. 이는 사용자 정의 게터(custom getters)를 사용하여 구현됩니다. 이렇게 하면 isFetching이나 isStale과 같은 속성이 자주 변경될 수 있지만 컴포넌트에서 사용되지 않는 경우 불필요한 리렌더링을 많이 피할 수 있습니다.

전역적으로 또는 쿼리별로 notifyOnChangeProps를 수동으로 설정하여 이 기능을 사용자 정의할 수 있습니다. 이 기능을 끄려면 notifyOnChangeProps: 'all'로 설정하면 됩니다.

참고: 사용자 정의 게터는 속성에 접근할 때 호출됩니다. 구조 분해 할당을 통해 접근하거나 직접 접근하는 방식 모두 가능합니다. 객체 rest 구조 분해 할당을 사용하면 이 최적화가 비활성화됩니다. 이 함정을 방지하기 위해 린트 규칙을 제공합니다.

select 옵션

select 옵션을 사용하여 컴포넌트가 구독해야 하는 데이터의 하위 집합을 선택할 수 있습니다. 이는 고도로 최적화된 데이터 변환을 수행하거나 불필요한 리렌더링을 피하는 데 유용합니다.

export const useTodos = (select) => {
  return useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    select,
  })
}

export const useTodoCount = () => {
  return useTodos((data) => data.length)
}
text

useTodoCount 커스텀 훅을 사용하는 컴포넌트는 할 일의 개수가 변경된 경우에만 리렌더링됩니다. 예를 들어, 할 일의 이름이 변경되어도 리렌더링되지 않습니다.

메모이제이션(Memoization)

select 함수는 다음과 같은 경우에만 다시 실행됩니다:

  • select 함수 자체가 참조적으로 변경된 경우
  • 데이터가 변경된 경우

이는 위의 예시처럼 인라인 select 함수를 사용하면 매 렌더링마다 실행된다는 것을 의미합니다. 이를 피하기 위해 select 함수를 useCallback으로 감싸거나, 의존성이 없는 경우 안정적인 함수 참조로 추출할 수 있습니다:

// useCallback으로 감싸기
export const useTodoCount = () => {
  return useTodos(useCallback((data) => data.length, []))
}
text
// 안정적인 함수 참조로 추출하기
const selectTodoCount = (data) => data.length

export const useTodoCount = () => {
  return useTodos(selectTodoCount)
}
text

추가 읽을거리

이러한 주제에 대해 더 자세히 알아보려면 커뮤니티 리소스에서 React Query 렌더링 최적화를 읽어보세요.