Skip to main content

Command Palette

Search for a command to run...

[번역] React.useDeferredValue() 는 어떻게 동작하나요?

React Internals Deep Dive - EP17

Published
3 min readView as Markdown
[번역] React.useDeferredValue() 는 어떻게 동작하나요?
T

Software engineer for web tech. Interested in sustainable growth as software engineer.

영문 블로그 글을 번역했습니다. 허가를 받으면 시리즈를 이어갈 예정입니다.
원문링크:
https://jser.dev/react/2022/01/26/how-does-react-usedeferredvalue-work


ℹ️React Internals Deep Dive 에피소드 17, 유튜브에서 제가 설명하는 것을 시청해주세요.

React@18.2.0기준, 최신 버전에서는 구현이 변경되었을 수 있습니다.

💬 역자 주석: JSer의 코멘트는 ❗❗로 표시 해뒀습니다.
그 외 주석은 리액트 소스 코드 자체의 주석입니다.
... 은 생략된 코드입니다.

React 동시 모드에서는 useTransition()Suspense 외에 useDeferredValue()라는 API가 하나 더 있는데, 이것이 무엇이고 어떻게 작동하는지 알아보겠습니다.

useDeferredValue() 는 어떤 문제를 해결하려고 하나요?

React 홈페이지에 자세한 답변이 있지만 데모가 깨져 있습니다.

여기서 간단히 시연해 보겠습니다.

useDeferredValue()가 없는 경우

데모는 useDeferredValue()가 없는 경우입니다.

Next 버튼을 클릭하면, 제목과 게시물에 대한 두 개의 API 목(Mock)이 실행되며, 제목 API는 더 빠르고(300ms) 게시물 API는 느리게(1000ms) 실행됩니다. 다음 버튼을 클릭하면 약 1000ms 후에 제목과 글이 모두 전환되는 것을 확인할 수 있습니다.

이는 버튼 클릭 핸들러가 useTransition()을 사용하고 있기 때문인데, 게시글 API가 데이터를 반환할 때까지 에러가 발생해서, 제목과 글이 모두 지연되기 때문입니다.

이것은 좋지 않은데, 제목이 더 빨리 표시되지 않는 이유는 무엇인가요?

useDeferredValue()가 있는 경우

데모는 useDeferredValue()가 있는 경우입니다.

Next 버튼을 다시 한번 클릭하면, 제목이 먼저 보여지고, 게시글이 따라오는 것을 볼 수 있습니다.

이게 훨씬 낫습니다.

useDeferredValue()를 직접 생성해보자

useTransition()의 작동 방식은 이미 다루었습니다. 간단히 말해서 에러가 발생하면 커밋을 중지하는 것입니다.

여기서 우리의 문제는, 제목 API가 데이터를 반환하면 React가 리렌더링을 시도하지만 게시물이 렌더링될 때 데이터가 준비되지 않았기 때문에 에러를 던져서 발생하는 문제입니다.

function ProfilePage({ resource }) {
  return (
    <React.Suspense fallback={<h1>Loading profile...</h1>}>
      <ProfileDetails resource={resource} />
      <React.Suspense fallback={<h1>Loading posts...</h1>}>
        <ProfileTimeline resource={resource} />
      </React.Suspense>
    </React.Suspense>
  );
}

이 문제를 해결하기 위해 게시물 섹션의 리소스를 state별로 캐시하고 useEffect()에서 업데이트할 수 있습니다.

function useDeferredValue(value) {
  const [state, setState] = React.useState(value);
  React.useEffect(() => {
    // since value might be promise which causes suspension
    // we should wrap it with startTransition
    React.startTransition(() => {
      setState(value);
    });
  }, [value]);
  return state;
}

됐습니다. 이제 코드를 우리가 구현한 곳으로 변경합니다.

function ProfilePage({ resource }) {
  const deferredResource = useDeferredValue(resource);
  return (
    <React.Suspense fallback={<h1>Loading profile...</h1>}>
      <ProfileDetails resource={resource} />
      <React.Suspense fallback={<h1>Loading posts...</h1>}>
        <ProfileTimeline resource={deferredResource} />
      </React.Suspense>
    </React.Suspense>
  );
}

열려있는 데모에서 우리가 만든 useDeferredValue() 함수를 사용하면 React.useDeferredValue()와 동일하게 작동합니다.

React.useDeferredValue()는 어떻게 동작 하나요?

debugger를 설정하면, 이전에 해왔던 것처럼 마운트용과 업데이트용 소스 코드를 찾을 수 있습니다.

function mountDeferredValue<T>(value: T): T {
  const [prevValue, setValue] = mountState(value);
  mountEffect(() => {
    const prevTransition = ReactCurrentBatchConfig.transition;
    ReactCurrentBatchConfig.transition = 1;
    try {
      setValue(value);
    } finally {
      ReactCurrentBatchConfig.transition = prevTransition;
    }
  }, [value]);
  return prevValue;
}

function updateDeferredValue<T>(value: T): T {
  const [prevValue, setValue] = updateState(value);
  updateEffect(() => {
    const prevTransition = ReactCurrentBatchConfig.transition;
    ReactCurrentBatchConfig.transition = 1;
    try {
      setValue(value);
    } finally {
      ReactCurrentBatchConfig.transition = prevTransition;
    }
  }, [value]);
  return prevValue;
}

잠깐만요, 기본적으로 우리가 작성한 것과 동일합니다! mountStateupdateStateuseState에 대한 구현일 뿐입니다.

도대체 ReactCurrentBatchConfig.transition이 무엇인지 궁금하실 텐데요, startTransition()의 소스를 보겠습니다. (소스)

export function startTransition(scope: () => void) {
  const prevTransition = ReactCurrentBatchConfig.transition;
  ReactCurrentBatchConfig.transition = 1;
  try {
    scope();
  } finally {
    ReactCurrentBatchConfig.transition = prevTransition;
  }
}

똑같습니다!!! 하지만 ReactCurrentBatchConfig.transition은 도대체 무엇을 하는 것일까요?

이 함수 내부의 업데이트는 트랜지션 lanes에서 예약되어야 하며, 일시 중단 시 커밋되지 않도록 React에 알려주는 내부 구현입니다.

자세한 내용은 제 동영상에서 확인할 수 있습니다.

(원본 게시일: 2022-01-26)

React Internals Deep Dive 번역 시리즈

Part 17 of 24

[번역] 리액트 내부 작동원리에 대한 JSer의 시리즈입니다. https://jser.dev/series/react-source-code-walkthrough

Up next

[번역] React에서 빈 값(empty values)은 어떻게 다뤄지나요?

React Internals Deep Dive - EP18

More from this blog

프론트엔드는 경계를 다루는 직무가 되어간다

프론트엔드는 원래도 복잡했습니다. 브라우저마다 다른 렌더링, 상태 동기화, 성능, 접근성 — 화면 하나 제대로 만드는 데 신경 쓸 게 늘 많았습니다. 다만 그 복잡함은 대체로 한 방향을 향했습니다. 이 UI를 어떻게 만들까, 상태를 어떻게 관리할까, 클릭하면 무엇이 바뀌어야 할까. 그런데 요즘 질문의 축이 하나 더 늘고 있습니다. 이 UI는 서버에서 먼저

Jul 20, 20266 min read

AI가 코드를 짜는 시대, 나는 리뷰를 못 따라가고 있었다

솔직히 말하면, 요즘 PR을 제때 리뷰하지 못하고 있습니다. 개발 속도는 빨라졌는데 리뷰 속도는 그대로입니다. 예전엔 하루 이틀이면 처리하던 게 이제는 며칠씩 밀립니다. 처음엔 제가 게을러진 탓인가 싶었습니다. 돌아보니 그게 아니었습니다. AI 도구를 쓰면서 코드가 쏟아지는 속도가 달라졌습니다. 한 사람이 만들어내는 코드 양이 예전과 비교가 안 됩니다. P

Mar 18, 20263 min read
AI가 코드를 짜는 시대, 나는 리뷰를 못 따라가고 있었다

테드풀의 한 입 지식

78 posts

가볍게 정리하는 기록들입니다.