본문 바로가기
C.W.K.
Stream
Lesson 02 of 06 · published

useDeferredValue — 무거운 결과를 뒤따라오게 하기

~11 min · usedeferredvalue, concurrent, rendering

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
useDeferredValue는 값의 이전 버전을 잠시 유지해 비싼 렌더링을 늦춰. 입력은 즉시 바꾸고 결과는 뒤따라오게 만드는 방법을 보자.

기본 모양

const deferred = useDeferredValue(value)라고 쓰면 첫 렌더링에서는 두 값이 같아. 이후 우선순위가 높은 렌더링이 진행되는 동안에는 deferred가 이전 값을 유지하고, React가 새 값으로 무거운 화면을 준비한 뒤 갱신돼.

값을 누가 바꾸는지에 따라 훅을 골라

직접 소유한 상태라서 setter를 감쌀 수 있다면 useTransition이 잘 맞아. 반대로 prop이나 Context처럼 이 컴포넌트가 변경 시점을 제어할 수 없는 값이라면 useDeferredValue로 그 값의 렌더링만 늦출 수 있어.

메모이제이션과 함께 써

비싼 자식 컴포넌트가 같은 prop을 받았을 때 렌더링을 건너뛸 수 있어야 지연된 값이 효과를 내. 자식을 React.memo로 감싸거나 React Compiler가 안전하게 메모이제이션하도록 해. 그렇지 않으면 부모가 렌더링될 때마다 비싼 자식도 함께 실행돼 이점이 줄어들어.

데이터를 늦추는 훅이 아니라 렌더링 순서를 조정하는 훅이야. 데이터는 이미 도착해 있어. React가 입력 화면을 먼저 그리고, 비용이 큰 결과 화면은 준비되는 대로 따라오게 만드는 거야. throttle이나 요청 지연과는 다르게 동작해.

이전 결과가 보인다는 사실을 표현해

query !== deferredQuery이면 입력은 새 값이지만 결과는 아직 이전 값을 보여 주는 중이야. 결과 영역의 opacity를 조금 낮추거나 갱신 중 문구를 표시하면 데이터가 오래됐다는 사실을 숨기지 않아.

Debounce와 목적이 달라

Debounce는 일정 시간 입력이 멈출 때까지 함수 호출이나 네트워크 요청을 줄여. useDeferredValue는 요청 횟수를 줄이지 않고 React 렌더링의 우선순위만 조정해. 필요하면 debounce로 요청을 줄이고 deferred value로 비싼 결과 렌더링을 늦추는 두 전략을 함께 쓸 수 있어.

Deferred value는 새 값이 준비될 때까지 이전 결과를 유지하지만 고정된 지연 시간을 보장하지 않아. 사용자 기기와 렌더링 비용에 따라 React가 갱신 시점을 정해.

Code

메모이제이션된 무거운 자식과 useDeferredValue·tsx
import { memo, useDeferredValue, useState } from "react";

function SearchPage() {
  const [query, setQuery] = useState("");
  // 지연된 값은 query를 뒤따라가므로 입력은 즉시 반응해.
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <div style={{ opacity: isStale ? 0.6 : 1 }}>
        <Results query={deferredQuery} />
      </div>
    </div>
  );
}

const Results = memo(function Results({ query }: { query: string }) {
  // 비싼 렌더링은 memo가 같은 query를 받으면 건너뛰어.
  // useDeferredValue와 함께 쓰면 input보다 덜 자주 다시 렌더링돼.
  return <ul>{filterLargeList(query).map((r) => <li key={r}>{r}</li>)}</ul>;
});

function filterLargeList(_q: string): string[] { return []; }

External links

Exercise

앞 레슨의 검색 화면을 useTransition 대신 useDeferredValue로 바꿔. 결과 컴포넌트는 React.memo로 감싸고 입력은 즉시, 결과는 조금 뒤에 바뀌는지 확인해. 그다음 memo를 제거해 두 기능을 함께 써야 하는 이유를 관찰해.
Hint
비싼 결과 컴포넌트에는 deferredQuery를 prop으로 넘겨. memo가 없으면 부모가 렌더링될 때마다 자식도 실행돼 지연 값의 이점이 줄어들어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.