본문 바로가기

이것저것 스터디📚/React - 공식 문서

React 공식문서 -useDeferredValue

- useDeferredValue : UI 일부의 업데이트를 지연시킬 수 있는 React 훅이다.

import { useState, useDeferredValue } from 'react';

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

- 업데이트가 발생하면, 지연된 값은 최신 값보다 "뒤쳐지게" 됩니다.

- React는 먼저 지연된 값을 업데이트하지 않은 채로 렌더링한 다음, 백그라운드에서 새로 받은 값으로 다시 렌더링을 시도한다.

 

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>
    </>
  );
}

- 위 예시에서 input 창에 "a"를 입력하고 결과를 기다린 다음 "ab"로 수정하는 경우 "a"에 대한 결과가 로딩 폴백으로 대체된다.

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>
    </>
  );
}

- 위와 같이 useDeferredValue를 사용하면 "a"를 입력하고 결과를 기다린 다음 "ab"로 수정하는 경우 "a"에 대한 결과가 로딩 폴백으로 대체되지 않고, "a"에 대한 검색 결과 목록을 표시한다.

 

How does deferring a value work under the hood?(값을 지연시키는 것은 어떻게 작동하나요?)

- useDeferredValue는 두 단계로 진행이 된다.

1. 위 예제에서 먼저 React는 query("ab")가 아닌 이전 deferredQuery(여전히 "a")로 다시 렌더링한다.

-> "a"에 대한 것으로 렌더링을 한번 다시 하긴 한다는 말인가?

2. 백그라운드에서 React는 query와 deferredQuery를 모두 "ab"로 업데이트한 상태로 리렌더링을 시도한다.

 

- 만약 "ab" 검색결과에 대한 작업이 일시 중단되면, React는 이 렌더링 시도를 포기하고 데이터가 로드된 후 이 렌더링을 다시 시도한다.

- 지연된 백그라운드 렌더링은 중단할 수 있다. 예를 들어, 사용자가 입력을 다시 시도하면 React는 해당 입력을 버리고 새 값으로 다시 시작한다. 이때 React는 항상 가장 최근에 제공받은 값을 사용한다.

- 다만, 각 키 입력마다 네트워크 요청은 여전히 존재한다. 즉, 지연되는 것은 네트워크 요청 자체가 아니라 결과가 준비될 때까지 결과를 표시하는 것이다.

- 사용자가 계속 입력하더라도 각 키 입력에 대한 응답은 캐시되므로 백스페이스를 누르면 즉시 다시 가져오지는 않는다.


Deferring re-rendering for a part of the UI(UI의 일부에 대해 리렌더링 연기하기)

- useDeferredValue를 성능 최적화로 적용할 수도 있다.

- 만약 UI의 일부가 리렌더링 속도가 느릴 때, 나머지 UI를 차단하지 않도록 하려는 경우에 유용하다.

- 예를 들어, 사용자의 입력에 따라, 결과 목록을 업데이트해야 하는 경우에, 결과 목록 업데이트보다 입력 업데이트의 우선 순위를 지정할 수 있다.

// 입력 업데이트의 우선순위를 지정
// input에 타이핑하면 타이핑은 빠르게 느껴지지만, 목록은 "지연"되는 것을 확인할 수 있다.
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} />
    </>
  );
}


// useDeferredValue가 없으면, 키 입력시마다 전체 목록이 중단되지 않는 방식으로 즉시 다시 리렌더링 된다.
// 따라서, input에 타이핑할 때 매우 뻑뻑한 느낌이 든다.
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} />
    </>
  );
}

- 다만 위와 같은 최적화를 위해서는 SlowList를 memo로 감싸야한다.

- 텍스트가 변경될 때마다 React가 부모 컴포넌트를 빠르게 다시 렌더링해야 하기 때문이다.

- 다시 렌더링하는 동안 deferredText는 여전히 이전 값을 가지므로 SlowList는 리렌더링을 건너뛸 수 있다.

- 만약 memo가 없다면 어쨌든 다시 렌더링해야 하므로 최적화의 취지가 무색해진다.

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;

How is deferring a value different from debouncing and throttling?(값을 연기하는 것은 디바운스 및 쓰로틀과 어떤 점이 다른가요?)

- 디바운스 : 사용자가 타이핑을 멈출 때까지 기다렸다가 목록을 업데이트하는 것을 의미

- 쓰로톨 : 가끔씩(예: 최대 1초에 한 번) 목록을 업데이트하는 것을 의미

 

- useDeferredValue는 React 자체와 깊게 통합되어 있고 사용자의 기기에 맞게 조정되기 때문에 렌더링을 최적화하는데 더 적합하다.

- 또한 디바운스나 쓰로틀과 달리 useDeferredValue에 의해 수행되는 지연된 리렌더링은 기본적으로 중단이 가능하다. 반면, 디바운스나 쓰로틀은 렌더링이 키 입력을 차단하는 순간을 연기할 뿐이다.

- 대신 디바운스나, 쓰로틀을 사용하면 네트워크 요청을 더 적게 실행할 수 있다.


* 참고 : React 공식문서(https://react-ko.dev/learn)