useDeferredValue

김동현·2026년 3월 17일

useDeferredValue

소개

useDeferredValue는 UI의 일부 업데이트를 지연시킬 수 있게 해주는 React Hook이에요.

const deferredValue = useDeferredValue(value)

목차


레퍼런스

useDeferredValue(value, initialValue?)

컴포넌트의 최상위 레벨에서 useDeferredValue를 호출하면 해당 값의 지연된 버전을 얻을 수 있어요.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  // ...
}

아래에서 더 많은 예제를 확인해보세요.

매개변수

  • value: 지연시키고 싶은 값이에요. 어떤 타입이든 가능해요.
  • 선택적 initialValue: 컴포넌트의 초기 렌더링 시에 사용할 값이에요. 이 옵션을 생략하면, useDeferredValue는 초기 렌더링 시에 지연시키지 않아요. 왜냐하면 대신 렌더링할 이전 버전의 value가 없기 때문이에요.

반환값

  • currentValue: 초기 렌더링 시에는 반환되는 지연된 값이 initialValue이거나, 여러분이 제공한 값과 동일할 거예요. 업데이트 시에는 React가 먼저 이전 값으로 리렌더링을 시도하고(그래서 이전 값을 반환해요), 그 다음에 백그라운드에서 새 값으로 또 다른 리렌더링을 시도해요(그래서 업데이트된 값을 반환해요).

주의사항

  • 업데이트가 Transition 안에 있을 때, useDeferredValue는 항상 새로운 value를 반환하고 지연된 렌더링을 생성하지 않아요. 왜냐하면 업데이트가 이미 지연되어 있기 때문이에요.

  • useDeferredValue에 전달하는 값은 원시 값(문자열이나 숫자 같은)이거나, 렌더링 바깥에서 생성된 객체여야 해요. 렌더링 중에 새 객체를 생성해서 바로 useDeferredValue에 전달하면, 매 렌더링마다 다른 객체가 되어서 불필요한 백그라운드 리렌더링을 일으키게 돼요.

  • useDeferredValue가 다른 값을 받으면 (Object.is로 비교해요), 현재 렌더링(아직 이전 값을 사용하는)에 더해서, 백그라운드에서 새 값으로 리렌더링을 예약해요. 백그라운드 리렌더링은 중단 가능해요: value에 또 다른 업데이트가 있으면, React는 백그라운드 리렌더링을 처음부터 다시 시작해요. 예를 들어, 사용자가 지연된 값을 받는 차트가 리렌더링할 수 있는 것보다 더 빠르게 입력창에 타이핑하면, 차트는 사용자가 타이핑을 멈춘 후에야 리렌더링될 거예요.

  • useDeferredValue<Suspense>와 통합되어 있어요. 새 값으로 인한 백그라운드 업데이트가 UI를 일시 중단시키면, 사용자는 폴백을 보지 않아요. 데이터가 로드될 때까지 이전의 지연된 값을 계속 보게 될 거예요.

  • useDeferredValue 자체만으로는 추가 네트워크 요청을 방지하지 않아요.

  • useDeferredValue 자체로 인한 고정된 지연은 없어요. React가 원래 리렌더링을 마치자마자, React는 즉시 새로운 지연된 값으로 백그라운드 리렌더링 작업을 시작해요. 이벤트로 인한 업데이트(타이핑 같은)는 백그라운드 리렌더링을 중단하고 그보다 우선 처리돼요.

  • useDeferredValue로 인한 백그라운드 리렌더링은 화면에 커밋될 때까지 Effect를 발생시키지 않아요. 백그라운드 리렌더링이 일시 중단되면, 그 Effect는 데이터가 로드되고 UI가 업데이트된 후에 실행될 거예요.


사용법

새로운 콘텐츠가 로딩되는 동안 이전 콘텐츠 보여주기

컴포넌트의 최상위 레벨에서 useDeferredValue를 호출해서 UI의 일부 업데이트를 지연시킬 수 있어요.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  // ...
}

초기 렌더링 시에는 지연된 값이 여러분이 제공한 값과 동일해요.

업데이트 시에는 지연된 값이 최신 값보다 "뒤처지게" 돼요. 특히, React는 먼저 지연된 값을 업데이트하지 않고 리렌더링하고, 그 다음에 백그라운드에서 새로 받은 값으로 리렌더링을 시도해요.

언제 이게 유용한지 예제를 통해 살펴볼게요.

참고

이 예제는 여러분이 Suspense가 활성화된 데이터 소스를 사용한다고 가정해요:

  • RelayNext.js 같은 Suspense 지원 프레임워크로 데이터 가져오기
  • lazy로 컴포넌트 코드 지연 로딩하기
  • use로 Promise의 값 읽기

Suspense와 그 제한 사항에 대해 더 알아보세요.

이 예제에서, SearchResults 컴포넌트는 검색 결과를 가져오는 동안 일시 중단돼요. "a"를 입력하고, 결과를 기다린 다음, "ab"로 수정해보세요. "a"에 대한 결과가 로딩 폴백으로 대체되는 걸 볼 수 있어요.

// src/App.js
import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}
// src/SearchResults.js
import {use} from 'react';
import { fetchData } from './data.js';

export default function SearchResults({ query }) {
  if (query === '') {
    return null;
  }
  const albums = use(fetchData(`/search?q=${query}`));
  if (albums.length === 0) {
    return <p>No matches for <i>"{query}"</i></p>;
  }
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}
// src/data.js (숨김)
// Note: the way you would do data fetching depends on
// the framework that you use together with Suspense.
// Normally, the caching logic would be inside a framework.

let cache = new Map();

export function fetchData(url) {
  if (!cache.has(url)) {
    cache.set(url, getData(url));
  }
  return cache.get(url);
}

async function getData(url) {
  if (url.startsWith('/search?q=')) {
    return await getSearchResults(url.slice('/search?q='.length));
  } else {
    throw Error('Not implemented');
  }
}

async function getSearchResults(query) {
  // Add a fake delay to make waiting noticeable.
  await new Promise(resolve => {
    setTimeout(resolve, 1000);
  });

  const allAlbums = [{
    id: 13,
    title: 'Let It Be',
    year: 1970
  }, {
    id: 12,
    title: 'Abbey Road',
    year: 1969
  }, {
    id: 11,
    title: 'Yellow Submarine',
    year: 1969
  }, {
    id: 10,
    title: 'The Beatles',
    year: 1968
  }, {
    id: 9,
    title: 'Magical Mystery Tour',
    year: 1967
  }, {
    id: 8,
    title: 'Sgt. Pepper\'s Lonely Hearts Club Band',
    year: 1967
  }, {
    id: 7,
    title: 'Revolver',
    year: 1966
  }, {
    id: 6,
    title: 'Rubber Soul',
    year: 1965
  }, {
    id: 5,
    title: 'Help!',
    year: 1965
  }, {
    id: 4,
    title: 'Beatles For Sale',
    year: 1964
  }, {
    id: 3,
    title: 'A Hard Day\'s Night',
    year: 1964
  }, {
    id: 2,
    title: 'With The Beatles',
    year: 1963
  }, {
    id: 1,
    title: 'Please Please Me',
    year: 1963
  }];

  const lowerQuery = query.trim().toLowerCase();
  return allAlbums.filter(album => {
    const lowerTitle = album.title.toLowerCase();
    return (
      lowerTitle.startsWith(lowerQuery) ||
      lowerTitle.indexOf(' ' + lowerQuery) !== -1
    )
  });
}
input { margin: 10px; }

일반적인 대안 UI 패턴은 결과 목록 업데이트를 지연시키고 새 결과가 준비될 때까지 이전 결과를 계속 보여주는 거예요. useDeferredValue를 호출해서 지연된 버전의 쿼리를 아래로 전달하세요:

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

query는 즉시 업데이트되어서, 입력창에 새 값이 표시돼요. 하지만 deferredQuery는 데이터가 로드될 때까지 이전 값을 유지해서, SearchResults가 잠시 동안 이전 결과를 보여줄 거예요.

아래 예제에서 "a"를 입력하고, 결과가 로드될 때까지 기다린 다음, 입력을 "ab"로 수정해보세요. 이제 Suspense 폴백 대신, 새 결과가 로드될 때까지 이전 결과 목록이 보이는 걸 확인할 수 있어요:

// src/App.js
import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}
// src/SearchResults.js
import {use} from 'react';
import { fetchData } from './data.js';

export default function SearchResults({ query }) {
  if (query === '') {
    return null;
  }
  const albums = use(fetchData(`/search?q=${query}`));
  if (albums.length === 0) {
    return <p>No matches for <i>"{query}"</i></p>;
  }
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}
// src/data.js (숨김)
// Note: the way you would do data fetching depends on
// the framework that you use together with Suspense.
// Normally, the caching logic would be inside a framework.

let cache = new Map();

export function fetchData(url) {
  if (!cache.has(url)) {
    cache.set(url, getData(url));
  }
  return cache.get(url);
}

async function getData(url) {
  if (url.startsWith('/search?q=')) {
    return await getSearchResults(url.slice('/search?q='.length));
  } else {
    throw Error('Not implemented');
  }
}

async function getSearchResults(query) {
  // Add a fake delay to make waiting noticeable.
  await new Promise(resolve => {
    setTimeout(resolve, 1000);
  });

  const allAlbums = [{
    id: 13,
    title: 'Let It Be',
    year: 1970
  }, {
    id: 12,
    title: 'Abbey Road',
    year: 1969
  }, {
    id: 11,
    title: 'Yellow Submarine',
    year: 1969
  }, {
    id: 10,
    title: 'The Beatles',
    year: 1968
  }, {
    id: 9,
    title: 'Magical Mystery Tour',
    year: 1967
  }, {
    id: 8,
    title: 'Sgt. Pepper\'s Lonely Hearts Club Band',
    year: 1967
  }, {
    id: 7,
    title: 'Revolver',
    year: 1966
  }, {
    id: 6,
    title: 'Rubber Soul',
    year: 1965
  }, {
    id: 5,
    title: 'Help!',
    year: 1965
  }, {
    id: 4,
    title: 'Beatles For Sale',
    year: 1964
  }, {
    id: 3,
    title: 'A Hard Day\'s Night',
    year: 1964
  }, {
    id: 2,
    title: 'With The Beatles',
    year: 1963
  }, {
    id: 1,
    title: 'Please Please Me',
    year: 1963
  }];

  const lowerQuery = query.trim().toLowerCase();
  return allAlbums.filter(album => {
    const lowerTitle = album.title.toLowerCase();
    return (
      lowerTitle.startsWith(lowerQuery) ||
      lowerTitle.indexOf(' ' + lowerQuery) !== -1
    )
  });
}
input { margin: 10px; }
Deep Dive: 값 지연이 내부적으로 어떻게 작동하나요?

두 단계로 일어난다고 생각하면 돼요:

  1. 먼저, React는 새로운 query ("ab")로 리렌더링하지만, 이전 deferredQuery (여전히 "a")를 사용해요. 결과 목록에 전달하는 deferredQuery 값은 지연된 거예요: query 값보다 "뒤처져" 있어요.

  2. 백그라운드에서, React는 querydeferredQuery 둘 다 "ab"로 업데이트된 상태로 리렌더링을 시도해요. 이 리렌더링이 완료되면, React는 그것을 화면에 보여줄 거예요. 하지만 일시 중단되면 ("ab"에 대한 결과가 아직 로드되지 않았다면), React는 이 렌더링 시도를 포기하고, 데이터가 로드된 후에 이 리렌더링을 다시 시도해요. 사용자는 데이터가 준비될 때까지 이전의 지연된 값을 계속 보게 될 거예요.

지연된 "백그라운드" 렌더링은 중단 가능해요. 예를 들어, 입력창에 다시 타이핑하면, React는 그것을 포기하고 새 값으로 다시 시작해요. React는 항상 가장 최근에 제공된 값을 사용해요.

각 키 입력마다 여전히 네트워크 요청이 있다는 점에 주의하세요. 여기서 지연되는 것은 결과 표시(결과가 준비될 때까지)이지, 네트워크 요청 자체가 아니에요. 사용자가 계속 타이핑하더라도, 각 키 입력에 대한 응답은 캐시되어서, Backspace를 누르면 즉시 반응하고 다시 fetch하지 않아요.


콘텐츠가 오래됐음을 표시하기

위의 예제에서는 최신 쿼리에 대한 결과 목록이 아직 로딩 중이라는 표시가 없어요. 새 결과가 로드되는 데 시간이 좀 걸리면 사용자에게 혼란스러울 수 있어요. 결과 목록이 최신 쿼리와 일치하지 않는다는 것을 사용자에게 더 명확하게 하려면, 이전 결과 목록이 표시될 때 시각적인 표시를 추가할 수 있어요:

<div style={{
  opacity: query !== deferredQuery ? 0.5 : 1,
}}>
  <SearchResults query={deferredQuery} />
</div>

이 변경으로, 타이핑을 시작하자마자 새 결과 목록이 로드될 때까지 이전 결과 목록이 살짝 어두워져요. 아래 예제처럼 CSS 전환을 추가해서 어두워지는 것을 지연시켜 점진적으로 느껴지게 할 수도 있어요:

// src/App.js
import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <div style={{
          opacity: isStale ? 0.5 : 1,
          transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear'
        }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}
// src/SearchResults.js
import {use} from 'react';
import { fetchData } from './data.js';

export default function SearchResults({ query }) {
  if (query === '') {
    return null;
  }
  const albums = use(fetchData(`/search?q=${query}`));
  if (albums.length === 0) {
    return <p>No matches for <i>"{query}"</i></p>;
  }
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}
// src/data.js (숨김)
// Note: the way you would do data fetching depends on
// the framework that you use together with Suspense.
// Normally, the caching logic would be inside a framework.

let cache = new Map();

export function fetchData(url) {
  if (!cache.has(url)) {
    cache.set(url, getData(url));
  }
  return cache.get(url);
}

async function getData(url) {
  if (url.startsWith('/search?q=')) {
    return await getSearchResults(url.slice('/search?q='.length));
  } else {
    throw Error('Not implemented');
  }
}

async function getSearchResults(query) {
  // Add a fake delay to make waiting noticeable.
  await new Promise(resolve => {
    setTimeout(resolve, 1000);
  });

  const allAlbums = [{
    id: 13,
    title: 'Let It Be',
    year: 1970
  }, {
    id: 12,
    title: 'Abbey Road',
    year: 1969
  }, {
    id: 11,
    title: 'Yellow Submarine',
    year: 1969
  }, {
    id: 10,
    title: 'The Beatles',
    year: 1968
  }, {
    id: 9,
    title: 'Magical Mystery Tour',
    year: 1967
  }, {
    id: 8,
    title: 'Sgt. Pepper\'s Lonely Hearts Club Band',
    year: 1967
  }, {
    id: 7,
    title: 'Revolver',
    year: 1966
  }, {
    id: 6,
    title: 'Rubber Soul',
    year: 1965
  }, {
    id: 5,
    title: 'Help!',
    year: 1965
  }, {
    id: 4,
    title: 'Beatles For Sale',
    year: 1964
  }, {
    id: 3,
    title: 'A Hard Day\'s Night',
    year: 1964
  }, {
    id: 2,
    title: 'With The Beatles',
    year: 1963
  }, {
    id: 1,
    title: 'Please Please Me',
    year: 1963
  }];

  const lowerQuery = query.trim().toLowerCase();
  return allAlbums.filter(album => {
    const lowerTitle = album.title.toLowerCase();
    return (
      lowerTitle.startsWith(lowerQuery) ||
      lowerTitle.indexOf(' ' + lowerQuery) !== -1
    )
  });
}
input { margin: 10px; }

UI 일부의 리렌더링 지연시키기

useDeferredValue를 성능 최적화로도 적용할 수 있어요. UI의 일부가 리렌더링하기 느리고, 최적화할 쉬운 방법이 없으며, 나머지 UI를 막는 것을 방지하고 싶을 때 유용해요.

텍스트 필드와 매 키 입력마다 리렌더링하는 컴포넌트(차트나 긴 목록 같은)가 있다고 상상해보세요:

function App() {
  const [text, setText] = useState('');
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={text} />
    </>
  );
}

먼저, props가 동일할 때 리렌더링을 건너뛰도록 SlowList를 최적화하세요. 이를 위해 memo로 감싸세요:

const SlowList = memo(function SlowList({ text }) {
  // ...
});

하지만, 이것은 SlowList props가 이전 렌더링과 동일할 때만 도움이 돼요. 지금 직면한 문제는 props가 다를 때, 그리고 실제로 다른 시각적 출력을 보여줘야 할 때 느리다는 거예요.

구체적으로, 주요 성능 문제는 입력창에 타이핑할 때마다, SlowList가 새로운 props를 받고, 전체 트리를 리렌더링하면 타이핑이 버벅거리게 느껴진다는 거예요. 이 경우, useDeferredValue를 사용하면 입력 업데이트(빨라야 하는)를 결과 목록 업데이트(더 느려도 되는)보다 우선할 수 있어요:

function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={deferredText} />
    </>
  );
}

이것은 SlowList의 리렌더링을 더 빠르게 만들지 않아요. 하지만, React에게 목록 리렌더링의 우선순위를 낮춰서 키 입력을 막지 않게 할 수 있다고 말해주는 거예요. 목록은 입력보다 "뒤처지고" 그 다음에 "따라잡을" 거예요. 이전과 마찬가지로, React는 가능한 한 빨리 목록을 업데이트하려고 하지만, 사용자가 타이핑하는 것을 막지는 않아요.

예제들: useDeferredValue와 최적화되지 않은 리렌더링의 차이

목록의 지연된 리렌더링

이 예제에서, SlowList 컴포넌트의 각 항목은 인위적으로 느리게 만들어져서 useDeferredValue가 어떻게 입력을 반응성 있게 유지하는지 볼 수 있어요. 입력창에 타이핑해보면 타이핑이 빠르게 느껴지면서 목록이 그 뒤를 "따라오는" 것을 확인할 수 있어요.

import { useState, useDeferredValue } from 'react';
import SlowList from './SlowList.js';

export default function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={deferredText} />
    </>
  );
}
// src/SlowList.js
import { memo } from 'react';

const SlowList = memo(function SlowList({ text }) {
  // Log once. The actual slowdown is inside SlowItem.
  console.log('[ARTIFICIALLY SLOW] Rendering 250 <SlowItem />');

  let items = [];
  for (let i = 0; i < 250; i++) {
    items.push(<SlowItem key={i} text={text} />);
  }
  return (
    <ul className="items">
      {items}
    </ul>
  );
});

function SlowItem({ text }) {
  let startTime = performance.now();
  while (performance.now() - startTime < 1) {
    // Do nothing for 1 ms per item to emulate extremely slow code
  }

  return (
    <li className="item">
      Text: {text}
    </li>
  )
}

export default SlowList;
.items {
  padding: 0;
}

.item {
  list-style: none;
  display: block;
  height: 40px;
  padding: 5px;
  margin-top: 10px;
  border-radius: 4px;
  border: 1px solid #aaa;
}
최적화되지 않은 목록 리렌더링

이 예제에서, SlowList 컴포넌트의 각 항목은 인위적으로 느리게 만들어졌지만, useDeferredValue가 없어요.

입력창에 타이핑하면 매우 버벅거리는 것을 확인할 수 있어요. 이것은 useDeferredValue 없이는 각 키 입력이 전체 목록을 중단 불가능한 방식으로 즉시 리렌더링하도록 강제하기 때문이에요.

import { useState } from 'react';
import SlowList from './SlowList.js';

export default function App() {
  const [text, setText] = useState('');
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={text} />
    </>
  );
}
// src/SlowList.js
import { memo } from 'react';

const SlowList = memo(function SlowList({ text }) {
  // Log once. The actual slowdown is inside SlowItem.
  console.log('[ARTIFICIALLY SLOW] Rendering 250 <SlowItem />');

  let items = [];
  for (let i = 0; i < 250; i++) {
    items.push(<SlowItem key={i} text={text} />);
  }
  return (
    <ul className="items">
      {items}
    </ul>
  );
});

function SlowItem({ text }) {
  let startTime = performance.now();
  while (performance.now() - startTime < 1) {
    // Do nothing for 1 ms per item to emulate extremely slow code
  }

  return (
    <li className="item">
      Text: {text}
    </li>
  )
}

export default SlowList;
.items {
  padding: 0;
}

.item {
  list-style: none;
  display: block;
  height: 40px;
  padding: 5px;
  margin-top: 10px;
  border-radius: 4px;
  border: 1px solid #aaa;
}

주의

이 최적화는 SlowListmemo로 감싸져 있어야 해요. text가 변경될 때마다 React가 부모 컴포넌트를 빠르게 리렌더링할 수 있어야 하기 때문이에요. 그 리렌더링 동안 deferredText는 여전히 이전 값을 가지고 있어서, SlowList는 리렌더링을 건너뛸 수 있어요 (props가 변경되지 않았으니까요). memo 없이는 어쨌든 리렌더링해야 해서, 최적화의 의미가 없어져요.

Deep Dive: 값 지연은 디바운싱과 쓰로틀링과 어떻게 다른가요?

이 시나리오에서 이전에 사용해봤을 수 있는 두 가지 일반적인 최적화 기법이 있어요:

  • 디바운싱은 목록을 업데이트하기 전에 사용자가 타이핑을 멈출 때까지 (예: 1초) 기다리는 거예요.
  • 쓰로틀링은 가끔씩 (예: 최대 1초에 한 번) 목록을 업데이트하는 거예요.

이런 기법들이 일부 경우에 유용하긴 하지만, useDeferredValue가 렌더링 최적화에 더 적합해요. 왜냐하면 React 자체와 깊이 통합되어 있고 사용자의 기기에 맞게 적응하기 때문이에요.

디바운싱이나 쓰로틀링과 달리, 고정된 지연을 선택할 필요가 없어요. 사용자의 기기가 빠르면 (예: 강력한 노트북), 지연된 리렌더링이 거의 즉시 일어나서 눈에 띄지 않을 거예요. 사용자의 기기가 느리면, 목록이 기기가 얼마나 느린지에 비례해서 입력보다 "뒤처질" 거예요.

또한, 디바운싱이나 쓰로틀링과 달리, useDeferredValue로 수행되는 지연된 리렌더링은 기본적으로 중단 가능해요. 이것은 React가 큰 목록을 리렌더링하는 중에 사용자가 또 다른 키 입력을 하면, React가 그 리렌더링을 포기하고, 키 입력을 처리한 다음, 다시 백그라운드에서 렌더링을 시작한다는 뜻이에요. 반면, 디바운싱과 쓰로틀링은 여전히 버벅거리는 경험을 만들어요. 왜냐하면 블로킹이기 때문이에요: 렌더링이 키 입력을 막는 순간을 단지 연기할 뿐이에요.

최적화하려는 작업이 렌더링 중에 일어나지 않는다면, 디바운싱과 쓰로틀링이 여전히 유용해요. 예를 들어, 더 적은 네트워크 요청을 발생시킬 수 있어요. 이 기법들을 함께 사용할 수도 있어요.

profile
프론트에_가까운_풀스택_개발자

0개의 댓글