딜레이를 지정하여 네트워크 요청을 제한하는 Debounce 와 throttle에 대해 공부하였다.
하지만 유저가 보는 화면, 디바이스마다 다른 사양에 따라 적절한 딜레이 값을 주는것은 어렵다.
React18 에서 fiber 라는 엔진을 개선하며 작업의 우선순위를 정할 수 있게 되었다.
예를들어 무겁고 유저경험에 중요하지 않은 작업은 우선순위를 낮춰 프레임률을 유지할 수 있다고 한다.
따로 디바운스, 쓰로틀 라이브러리나 커스텀훅을 만들지 않고도 리액트에서 제공하는 훅을 사용하여 해결할 수 있다.
🧩 useTransition
화면이 끊기지 않은 상태에서 상태를 업데이트 할 수 있는 Hook.
함수 실행의 우선순위를 지정하는 특징이 있다.
const [isPending, startTransition] = useTransition(); const [tab, setTab] = useState('about'); function selectTab(nextTab) { startTransition(() => { setTab(nextTab); }); }
- startTransition : 상태가 업데이트 될 때 전환되는 콜백함수
- 낮은 우선순위로 실행할 함수를 인자로 받는다.
- isPending : 보류상태 표시
- 작업이 지연되고 있음을 알릴 수 있고 이때 Spinner , Skeleton 을 사용하여 화면이 비어보이지 않게끔 한다.
🌟 이럴때 사용한다.
- blocking 없이 상태 업데이트하기
- 렌더링이 늦은 구간에서도 유저 인터페이스는 작동을 유지할 수 있다.
- 상태전환 시 상위 구성요소 업데이트
- 이벤트 핸들러 내에서도 업데이트 가능
- 상태전환 보류(pending) 일때 시각적으로 보여주기
- 원치않는 로딩표시 방지
- <Suspense/> 만 이용했을 시 원치않은 구간까지 loading 처리가 될 수 있는 부분을, Transition을 이용하여 제어할 수 있다.
- Suspense 지원 라우터 구축
🚨 주의사항
- set상태의 값만 업데이트 할 수 있다. 만약 props 로 받은값이나 커스텀훅의 리턴값으로 전환하고 싶다면
useDeferredValue
를 사용하면 된다.
- 전달받은 set상태는 동기적이어야 한다.
startTransition(() => { // ✅ Setting state *during* startTransition call setPage('/about'); }); startTransition(() => { // ❌ Setting state *after* startTransition call setTimeout(() => { setPage('/about'); }, 1000); });
- 트랜지션 함수에서 사용된 상태는 다른 상태 업데이트에 의해 중단된다.
- 텍스트 입력을 제어하는데 적절하지 않다.
- 여러개의 Transition 이 있을 경우 동기적으로 Transition 을 처리한다 → 릴리즈 후 개선예정이라고 한다.
🧩 useDeferredValue
업데이트 되는 부분의 UI 를 딜레이 시킬 수 있는 React Hook.
값의 업데이트 우선순위를 지정한다. 우선순위가 높은 작업을 실행하는 동안 이전의 값을 계속 가지고 있으며 업데이트를 지연시킨다.
import { useState, useDeferredValue } from 'react'; function SearchPage() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); // ... }
- deferredQuery : 지연된 값
- query : 전달받은 인자로, 제공된 값 즉 최신 값이 된다.
- 지연된 값을 먼저 업데이트 하지 않고 리렌더링 먼저 한 후 백그라운드에서 받은 값으로 리렌더링
🌟 이럴때 사용한다.
- 새로운 데이터가 로드되는 동안 기존콘텐츠 표시
- 새로운 데이터 입력 시 기존데이터가 최신이 아니라는것을 보여줄때
const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); const isStale = query !== deferredQuery; <> <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 는 <Suspense /> 와 같이 사용될 수 있다.
- 새로 업데이트 된 값으로 인해 UI 가 일시중단 될때 같이사용하게 된다면 Suspense의 fallback 이 보여지지 않고 데이터가 로드될때까지 이전 지연값이 계속 표시된다.
- 전달받은 값은 문자열, 숫자, 렌더링 외부에서 생성된 객체만 가능.
- 렌더링 중 새 객체를 생성하고 바로 전달 시 useDeferredValue를 렌더링할때마다 달라지기 때문에 불필요한 리렌더링이 발생한다.
- 자체적으로 발생하는 지연은 없으나 입력과 같은 이벤트로 인한 모든 업데이트는 리렌더링을 중단하고 백그라운드에서 우선순위를 갖게 된다.
🤔 디바운스와 차이점은 뭘까
디바운스 : 일정 시간만큼 Delay 시키는 최적화 기술
스로틀링 : 일정 시간만큼 Update 하는 최적화 기술
기기에 따라, 네트워크 상황에 따라 적절한 딜레이 시간이 달라지는 위의 기술들과 달리, useDeferredValue 는 리액트 자체 내에서 작업시간을 파악하여 적용시키기 때문에 렌더링 최적화에 더 적합하다.
그렇기 때문에 고정적인 time delay 를 넣지 않아도 된다.
또한, useDeferredValue는 어떠한 이벤트가 치고 들어왔을때 “중단” 하는 개념이다.
리액트가 어떤 거대한 목록을 리렌더링 중에 유저가 다른 입력값을 입력한다면
리액트는 리렌더링을 중단하고 입력값을 처리한 다음 백그라운드에서 입력값을 처리한다.
반면 디바운스와 스로틀링은 입력을 “차단” 하는 개념이라 유저에게 불안정한 경험을 줄 수 있다.
하지만 더 적은수의 네트워크 요청을 실행하거나 최적화 작업이 렌더링 중 일어나지 않을경우에 디바운스와 스로틀링은 여전히 유용할 수 있다.