useDefferedValue란?
특정 값의 업데이트를 지연시키면서, UI가 더 부드럽게 업데이트 되도록 도와준다. 이를 통해 사용자는 브라우저가 작업을 완료하기를 기다리지 않고, 즉각적인 반응을 경험할 수 있다.
주로 사용자가 입력을 하거나, 스크롤 할 때 처럼 빠르게 변화하는 값을 처리할 때 유용하다.
useDeferredValue
UI 일부 업데이트를 지연시킬 수 있는 리액트 훅
const deferredValue = useDeferredValue(value)
레퍼런스
컴포넌트의 최상위 레벨에서 useDefferedValue를 호출하여 지연된 버전의 값을 가져온다
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
매개변수
value: 지연시키려는 값으로 모든 타입을 가질 수 있다.
반환값
currrentValue: 초기 렌더링중에 반환된 '지연된 값'은 사용자가 제공한 값과 같다. 업데이트가 발생하면 React는 먼저 이전 값으로 리렌더링을 시도(반환갑이 이전 값과 일치하도록)하고, 그 다음 백그라운드에서 다시 새 값으로 리렌더링을 시도(반환값이 업데이트된 새 값과 일치하도록)한다.
주의사항
- 원시 값 또는 외부에서 생성된 객체 사용
- useDeffered에 전달하는 값은 문자열, 숫자와 같은 원시 값이거나, 컴포넌트 외부에서 생성된 객체여야 한다.
- 렌더링 중에 새로운 객체를 생성하고 즉시 useDefferedValue에 전달하면, 매번 다른 객체로 인식되어 불필요한 백ㄷ라운드 리렌더링이 발생할 수 있다.
- 백그라운드 리렌더링
- useDefferedValue는 새로운 값(Object.js로 비교)을 받으면 백그라운드에서 리렌더링을 예약한다.
- 만약 값이 다시 업데이트 되면, 이전 백그라운드 리렌더링은 중단되고 새로운 값으로 다시 시작된다.
- 예를 들어, 사용자가 입력하는 속도가 차트가 리렌더링 가능한 지연된 값을 받는 속도보다 빠르면, 차트는 사용자가 입력을 멈춘 후에만 리렌더링 된다.
- <Suspense>와 통합
- 새로운 값으로 인한 백그라운드 업데이트가 UI를 일시중단 시키면, 사용자는 폴백(fallback)을 보지 않는다. 대신 데이터가 로딩될 때 까지 이전 지연된 값이 표시된다.
- 추가 네트워크 요청 방지:
- 자체로 추가 네트워크 요청을 방지하지 않는다, 이를 위해서는 별도의 로직이 필요하다
- 고정된 지연 없음
- 자체로 인해 고정된 지연은 없다. 리액트는 원래 리렌더링이 완료되자마자 새로운 지연된 값으로 백그라운드 리렌더링을 즉시 시작한다.
- 이벤트로 인한 업데이트(ex.타이핑)은 백그라운드 리렌더링을 중단하고 우선적으로 처리된다.
- Effects 실행시점
- useDefferedValue로 인한 백그라운드 리렌더링은 화면에 커밋될 때 까지 Effects를 실행하지 않는다.
- 백그라운드 리렌더링이 일시 중단되면 데이터가 로딩되고 UI가 업데이트 된 후에 Effects가 실행된다.
사용법
새 콘텐츠가 로딩되는 동안 오래된 콘텐츠 표시하기
컴포넌트 최상위 레벨에서 useDeferredValue를 호출하여 UI일부 업데이트를 지연할 수 있다.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
초기 렌더링 중에 지연된 값은 사용자가 제공한 값과 일치하다.
업데이트가 발생하면 지연된 값은 최신 값보다 뒤쳐지게 된다. 리액트는 먼저 지연된 값을 업데이트하지 않은 채로 렌더링한 다음 백그라운드에서 새로 받은 값으로 리렌더링을 시도한다.
중요!
이 예제에서 Suspense 지원 데이터 소스 중 하나를 사용한다고 가정하자.
- Relay와 Next.js 같이 Suspense가 가능한 프레임워크를 사용한 데이터 가져오기
- lazy를 활용한 지연 로딩 컴포넌트
- use를 사용해서 Promise 값 읽기
이 예시에서는 검색 결과를 불러오는 동안 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>
</>
);
}
일반적인 대체 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는 즉시 업데이트되므로 input에 새 값이 표시된다. 그러나 deferredQuery는 데이터가 로딩될 때까지 이전 값을 유지하므로 SearchResults는 잠시 동안 오래된 결과를 표시한다.
아래 예시에서 "a"를 입력하고 결과가 로딩될 때까지 기다린 다음, 입력값을 "ab" 로 수정해보자. 이제 새 결과가 로딩될 때까지 Suspense 폴백 대신 오래된 결과 목록이 표시되는 것을 확인할 수 있다.
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>
</>
);
}
콘텐츠가 오래되었음을 표시하기
위의 예에서는 최신 쿼리에 대한 결과 목록이 아직 로딩중이라는 표시가 없다. 새 결과를 로딩하는데 시간이 오래 걸리는 경우 사용자에게 혼란을 줄 수 있다. 결과 목록이 최신 쿼리와 일치하지 않는다는 것을 사용자에게 더 명확하게 알리기 위해, 오래된 결과 목록이 표시될 때 시각적 표시를 추가할 수 있다.
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>
이렇게 변경하면 입력을 시작하자마자 새 결과 목록이 로딩될 때까지 오래된 결과 목록이 약간 어두워진다. 아래 예시에서와 같이 점진적으로 어두워진다고 느껴지도록 CSS transition을 추가하여 흐리게 표시되는 것을 지연시킬 수도 있다.
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>
</>
);
}
UI 일부에 대해 리렌더링 지연하기
useDeferredValue 를 성능 최적화로 적용할 수도 있다. UI 일부가 리렌더링 속도가 느리고, 이를 최적화할 쉬운 방법이 없으며, 나머지 UI를 차단하지 않도록 하려는 경우에 유용하다.
키 입력 시마다 리렌더링되는 텍스트 필드와 컴포넌트(ex.차트 또는 긴 목록)가 있다고 상상해보자
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가 다르고 실제로 다른 시각적 출력을 보여줘야 할 때 속도가 느리다는 것이다.
구체적으로, 주요 성능 문제는 input에 타이핑할 때 마다 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의 리렌더링 속도가 빨라지지는 않는다. 하지맞 키 입력을 차단하지 않도록 목록 리렌더링의 우선순위를 낮출 수 있다는것을 리액트에 알려준다. 목록은 입력보다 지연되었다가 따라잡을 것입니다. 이전과 마찬가지로 리액트는 가능한 한 빨리 목록을 업데이트 하려고 시도하지만, 사용자가 입력하는 것을 차단하지는 않는다.
주의!
이 최적화를 위해서는 SlowList를 memo로 감싸야 한다. text가 변경될 때마다 리액트는 부모 컴포넌트를 빠르게 리렌더링할 수 있어야 하기 때문이다. 리렌더링 하는 동안 deferredText는 여전히 이전 값을 가지므로 SlowList는 리렌더링을 건너뛸 수 있다(props는 변경되지 않는다). memo가 없으면 어쨌든 리렌더링해야 하므로 최적화의 취지가 무색해진다.
'React 공식문서 스터디' 카테고리의 다른 글
useTransition, useDefferedValue 발표자료 (0) | 2024.06.27 |
---|---|
useTransition (0) | 2024.06.24 |
useMemo, useCallback 발표자료 (0) | 2024.06.21 |
useCallback (1) (0) | 2024.06.18 |
useMemo (2) (0) | 2024.06.18 |