본문으로 건너뛰기

렌더링 최적화

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

구조적 공유

React Query는 "구조적 공유"라는 기법을 사용하여 재렌더링 사이에 가능한 한 많은 참조가 그대로 유지되도록 합니다. 네트워크를 통해 데이터를 가져오면 일반적으로 응답을 JSON으로 파싱하는 과정에서 완전히 새로운 참조를 얻게 됩니다. 하지만 데이터에서 아무것도 변경되지 않았다면 React Query는 원래 참조를 유지합니다. 일부가 변경되었다면 React Query는 변경되지 않은 부분을 유지하고 변경된 부분만 교체합니다.

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

참조 동일성

useQuery, useInfiniteQuery, useMutation에서 반환되는 최상위 객체와 useQueries에서 반환되는 Array는 참조적으로 안정적이지 않습니다. 렌더링할 때마다 새로운 참조가 됩니다. 하지만 이러한 훅에서 반환되는 data 속성은 가능한 한 안정적으로 유지됩니다.

추적되는 속성

React Query는 useQuery에서 반환된 속성 중 하나가 실제로 "사용된" 경우에만 다시 렌더링을 트리거합니다. 이는 Proxy 객체를 사용해 구현합니다. 예를 들어 isFetching 또는 isStale 같은 속성은 자주 변경될 수 있지만 컴포넌트에서 사용되지 않으므로, 이를 통해 불필요한 다시 렌더링을 상당히 줄입니다.

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

참고: 프록시의 get 트랩은 구조 분해를 통하거나 직접 접근하여 속성에 접근할 때 호출됩니다. 객체 나머지 구조 분해를 사용하면 이 최적화가 비활성화됩니다. 이 함정을 방지하기 위한 lint 규칙이 있습니다.

select

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

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

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

useTodoCount 사용자 지정 훅을 사용하는 컴포넌트는 할 일 목록의 길이가 변경될 때만 다시 렌더링됩니다. 예를 들어 할 일의 이름이 변경되어도 **다시 렌더링되지 **않습니다.

참고: select는 성공적으로 캐시된 데이터에 작동하며 오류를 발생시키기에 적절한 위치가 아닙니다. 오류의 신뢰 가능한 원본은 queryFn이며, 오류를 반환하는 select 함수는 dataundefined가 되고 isSuccesstrue가 되는 결과를 초래합니다. 잘못된 데이터로 인해 쿼리가 실패하도록 하려면 queryFn에서 오류를 처리하고, 캐싱과 관련 없는 오류 사례가 있다면 쿼리 훅 외부에서 처리하는 것을 권장합니다.

메모이제이션

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

  • select 함수 자체의 참조가 변경되었습니다
  • data 변경됨

이는 위에 표시된 것처럼 인라인된 select 함수가 렌더링할 때마다 실행된다는 의미입니다. 이를 방지하려면 select 함수를 useCallback으로 감싸거나, 의존성이 없다면 안정적인 함수 참조로 추출할 수 있습니다:

// wrapped in useCallback
export const useTodoCount = () => {
return useTodos(useCallback((data) => data.length, []))
}
// extracted to a stable function reference
const selectTodoCount = (data) => data.length

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

추가 자료

이 주제들에 관한 심층 가이드는 다음의 React Query 렌더링 최적화를 읽어보세요 TkDodo입니다. select 옵션을 가장 효과적으로 최적화하는 방법은 React Query 셀렉터 강화하기를 참조하세요