[멋쟁이사자처럼 부트캠프 프론트엔드 14기/REACT] useDeferredValue

minji·2025년 9월 17일
post-thumbnail

"엇박자의 조화로움을 보여줄게"


예고편 되돌아보기 🎥

Suspense 글의 마무리에서 예고했던 내용을 되돌아보자.

검색어를 입력하는 상황에서 '사' 라고 입력하고 다음 글자로 '과'를 입력하는데 지연이 발생했다고 하자.
자동완성기능에 '사'로 시작하는 단어들이 나왔다가 '과'를 입력하니까 '로딩중...' 이라고 렌더링이 됐다면?
사용자는 😲"이게뭐지?" 하며 어리둥절해할 것이고, 결국 사용자경험(UX)도 좋지않을 것이다.

이럴 때는 '사'라고 입력했을 때의 결과. 즉, 이전 값을 유지한채 '과'에 대한 결과값 로딩을 기다리는 것이 사용자경험 측면에서 더 좋은 방향이다.


위 예고편의 문제(사용자 경험 저하)를 해결하는 방법 중 하나로 memo 함수로 느리게 렌더링되는 컴포넌트를 감싸는 방법이 있다.

이 방법으로 불필요한 렌더링을 줄일 순 있으나, 입력값이라는 props는 변경되지 않았으므로 여전히 렌더링이 느려 근본적인 해결책이 되기엔 아쉬운 부분이 있다.

이 때, 예고했던 훅을 사용하면 즉각적인 업데이트가 필요한 부분과 조금은 느려도 되는 부분을 분리하여 문제를 해결할 수 있다! (살펴봅시다)

useDeferredValue란?

| 일부 UI 업데이트 반응속도를 지연시킬 수 있는 리액트 훅이다.

// 형태
const deferredValue = useDeferredValue(value, initialValue?)

매개변수

value

  • 지연시키려는 값
  • 모든 타입을 값으로 가질 수 있다.

(optional) initialValue

  • 컴포넌트 초기 렌더링시 사용할 값
  • 옵션 생략시, 초기 렌더링동안은 값을 지연시키지 않는다.
  • 리액트 19버전부터 설정이 가능해졌다. → 초기 렌더링시 undefined 가 아닌 설정한 값으로 시작할 수 있게 되었다.

반환값

currentValue

  • 초기 렌더링 중 반환된 지연된 값은 사용자가 제공한 값동일하다.

이러한 useDeferredValue 의 대표적인 사용예시는 다음과 같으며, 이 예시를 생각하면서 내용을 살펴보자.


import { useState } from 'react'
import SlowChild from './slow-child'

export default function App() {
  const [query, setQuery] = useState('')

  return (
    <div className="app">
      <label htmlFor="search-input">
        검색어 입력
      </label>
      <input
        id="search-input"
        type="text"
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="검색어를 입력하세요."
      />
      <SlowChild query={query} />
    </div>
  )
}

// SlowChild.tsx
export default function SlowChild({ query }) {
	
	// start 변수 컴포넌트가 렌더링될 때 시간(ms) 저장
  const start = performance.now()
  
  // 현재 시간에서 start 변수 값을 뺀 값이 
  // 100ms보다 작을 동안 아무 작업도 하지 않고 계속 반복
  // 즉, 아무 일도 않고 100ms 동안 CPU를 점유
  while (performance.now() - start < 100) {
    // 성능 저하 시뮬레이션
  }

  return <p>검색어: {query}</p>
}

Suspense와의 관계

Suspense로 감싼 컴포넌트를 렌더링할 때 새로운 검색어를 입력하면 '로딩 중...'만 보여주었다.

하지만 useDeferredValue와 함께라면, 새 데이터가 준비될 때까지 이전 데이터(지연된 값)를 렌더링할 수 있다.

✋🏻잠깐! 그럼 Suspense를 굳이 왜 쓰는거야?

useDeferredValue 의 반환값인 지연된 값입력값이 다를 수가 있는데, 이 때는 보여줄 이전 데이터가 없어진다.
왜냐면 useDeferredValue 훅은 별도의 로딩 상태를 제공하지않기 때문이다.
이럴 때 짜잔-! 하고 보여주려고 Suspense 가 든든하게 컴포넌트를 감싸고 대기중인 것이다. (마니또 느낌)


useDeferredValue의 특징 및 유의사항

1. 실제 성능 병목지점 파악하기

  • 입력과 동시에 렌더링 되는 컴포넌트가 실제로 성능 문제를 일으키는지 개발자도구-Profiler 를 통해서 확인해야한다.
  • 즉시 렌더링 되어야하는 컴포넌트인지 구분지을 수 있어야 한다.

2. 입력영역과 결과영역 렌더링 분리하기

  • 입력영역은 즉시 업데이트가 되어야하고, 결과를 보여주는 영역은 나중에 보여지게 하는 것이 중요하다. → 대용량 데이터 처리에 매우 효과적 ✨

3. UI 우선순위를 자동으로 조정

  • 훅을 사용하면 빠른 작업느린 작업 을 구분지어 우선순위 를 자동으로 조정한다.
  • 쓰로틀(Throttle) 과 디바운스(Debounce) 를 사용하지 않고도 부드러운 사용자 경험을 만들 수 있다.

✋🏻잠깐! 쓰로틀(Throttle) 과 디바운스(Debounce)가 뭔데?

곧 내용 업데이트 예정입니다

4. 이전 데이터로 렌더링 유지가능

  • Suspense 와 함께 사용시, 새로운 데이터를 불러오는 동안 '로딩 중...' 대신 이전 데이터를 보여줄 수 있다. → 시너지 효과 뿜뿜!
  • 데이터의 로딩준비가 완료되면 자동으로 새로운 데이터로 교체된다.

5. 캐싱전략도 챙기기

  • 동일 입력값에 대한 캐싱을 적용하면 불필요한 네트워크 요청과 렌더링을 줄이는게 효과적이다. (useDeferredValue 은 그 자체로 추가 네트워크 요청 방지를 하지 않는다.)

6. Effect 일시중단 시키기

  • useDeferredValue 로 인한 백그라운드 리렌더링이 화면에 커밋 될 때까지 Effect 는 실행되지 않는다.
    (백그라운드 리렌더링 일시 중단 → 데이터 로딩 → UI 업데이트 → Effect 실행)

7. 가장 최근에 제공(입력)받은 값이 기준

  • 리액트는 항상 가장 최근에 제공(입력)받은 값을 기준으로 사용한다.
    예를 들어, 검색어를 '바' 입력 → '나' 입력 → '나' 지우고 '보' 입력 이렇게 수정입력했다고하면 리액트는 '바나' 까지를 지연된 값으로 저장해놨다가 '나' 를 지우고 '보' 를 입력하는 순간 저장해둔 '바나' 를 지우고 새로운 값인 '바보'로 지연된 값을 업데이트 하는 것이다.

정리하자면,

1. useDeferredValue 의 사용목적은 분리 이다.

  • 즉각적으로 반응하길 원하는 값과 잠시 대기했다가 보여주길 원하는 값의 분리

2. memo 와 같이 사용해야 최적화가 이루어진다.

  • memo 로 감싸지 않을 경우, 입력값이 변경될 때마다 부모 컴포넌트가 리렌더링 되어야 한다.
  • props 변경까지 memo 가 캐치해줄 수는 없지만, memo 마저 없으면 최적화의 취지가 무색해진다.

🔗 참고자료
https://ko.react.dev/reference/react/useDeferredValue

profile
FE Developer .mjs

0개의 댓글