useDeferredValue는 UI 일부 업데이트를 지연시킬 수 있는 React Hook이다.
const deferredValue = useDeferredValue(value)
레퍼런스
useDeferredValue(value)
컴포넌트의 최상위 레벨에서 useDeferredValue를 호출하여 지연된 버전의 값을 가져온다.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
매개변수
- value : 지연시키려는 값이다. 모든 타입을 가질 수 있다.
반환값
초기 렌더링 중에는 반환된 '지연된 값'은 사용자가 제공한 값과 같다.
업데이트가 발생하면 이전 값으로 리렌더링을 시도 하고, 그 다음 백그라운드에서 다시 새 값으로 리렌더링을 시도 한다.
주의사항
- useDeferredValue에 전달하는 값은 문자열 및 숫자와 같은 원시값이거나, 컴포넌트의 외부에서 생성된 객체여야 한다. 렌더링 중에 새 객체를 생성하고 즉시 훅에 전달하면 렌더링 할 때마다 값이 달라져 불필요한 백그라운드 리렌더링이 발생할 수 있다.
- useDeferredValue는 <Suspense>와 통합된다. 새로운 값으로 인한 백그라운드 업데이트로 인해 UI가 일시 중단되면 사용자는 폴백을 볼 수 없다.
...
사용법
새 콘텐츠가 로딩되는 동안 오래된 콘텐츠 표시하기
컴포넌트의 최상위 레벨에서 호출하여 UI일부 업데이트를 지연할 수 있다.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
초기 렌더링 중에 지연된 값은 사용자가 제공한 값과 일치한다.
업데이트가 발생하면 지연된 값은 최신 값보다 뒤쳐지게 된다.
React는 먼저 지연된 값을 업데이트 하지 않은 채로 렌더링한 다음, 백그라운드에서 새로 받은 값으로 리렌더링을 시도한다.
중요한점은
중요합니다!
이 예제에서는 Suspense 지원 데이터 소스 중 하나를 사용한다고 가정합니다
이 예시에서는 검색 결과를 불러오는 동안 SearchResults 컴포넌트가 일지중지 된다.
"a"를 입력하고 결과를 기다린 다음 "ab"로 수정해 보세요. "a"에 대한 결과가 로딩 폴백으로 대체될 것입니다.
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>
</>
);
}
하지만 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는 즉시 업데이트 되므로 input에 새 값이 표시된다.
그러나 deferredQuery는 데이터가 로딩될 때까지 이전 값으로 유지하므로 SearchResults는 잠시 동안 이전 결과를 보여준다.
이제 새 결과가 로딩될 때까지 Suspense 폴백 대신 오래된 결과 목록이 표시되는 것을 확인할 수 있다.
값을 지연시키는 것이 내부적으로는 어떻게 작동하나?
두 단계로 진행된다고 생각하면 된다.
1. 먼저 React는 새로운 query("ab")로 리렌더링을 하지만 이전 값을 여전히 사용한다. 결과 목록에 전달하는 deferredQuery 값은 지연된 값이다.
2. 백그라운드에서 React는 query와 deferredQuery를 모두 업데이트한 상태로 리렌더링을 시도한다. 이 리렌더링이 완료되면 React는 이를 화면에 표시한다. 그러나 일시 중단되는 경우 React는 이 렌더링 시도를 포기하고, 데이터가 로딩된 후 이 리렌더링을 다시 시도한다.
콘텐츠가 오래되었을을 표시하기
위에 사용한 코드에서 새 결과를 로딩하는 데 시간이 오래 걸리는 경우 사용자에게 혼란을 줄 수 있다.
이를 방지하기 위해 사용자에게 더 명확하게 알려야 한다.
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>
이렇게 해주면 입력을 시작하면 결과가 뜨기 전까지 투명도가 바뀌게 된다.
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가 이전 렌더링 때와 동일한 경우에만 도움이 된다
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에 알려준다.
주의할점은
이 최적화를 위해서는 SlowList를 memo로 감싸야 합니다. text가 변경될 때마다 React는 부모 컴포넌트를 빠르게 리렌더링할 수 있어야 하기 때문입니다. 리렌더링하는 동안 deferredText는 여전히 이전 값을 가지므로 SlowList는 리렌더링을 건너뛸 수 있습니다(props는 변경되지 않았습니다). memo가 없으면 어쨌든 리렌더링해야 하므로 최적화의 취지가 무색해집니다.
'리액트 공식문서 읽어보기' 카테고리의 다른 글
| useId (0) | 2024.03.07 |
|---|---|
| useDebugValue (0) | 2024.03.03 |
| useCallback (0) | 2024.03.02 |
| <Suspense> (1) | 2024.02.25 |
| lazy (0) | 2024.02.25 |