`프론트엔드 개발자` 개형이의 벽돌집

프론트엔드 학습 일지 Day 2 - 리액트 동시성 React Concurrency 본문

학습일지

프론트엔드 학습 일지 Day 2 - 리액트 동시성 React Concurrency

개형이 2023. 8. 2. 13:38

리액트 18 버전과 함께 등장한 동시성 적용에 대해 학습해보고자 한다.

 

아직 실무에서 적용해본 적은 없는 내용이라 배움에 상당한 기대가 된다.

 

Day 2 스탓뚜!

 

 

 

리액트 동시성 (Concurrency) 살펴보기


 

1. 동시성이란?

 

리액트는 기본적으로 다음 뷰를 렌더링할 때 UI를 Blocking 했었다. UI를 블로킹.. 말 그대로 다음 뷰를 렌더링하는 동안 블로킹이 걸려 현재 뷰의 어떤 동작을 수행할 수 없는 것이다. 만약 어떤 컴포넌트 Rendering에 시간이 오래 소요된다면, 기다리는 동안 다른 것을 클릭할 수 없을 것이다. 오래 걸린다고 해서 중단하고 싶어도 중단할 수도 없을 것이다. 하지만 동시성이 적용된다면?! 

 

다음 뷰를 렌더링하는 동안 현재 뷰의 반응성을 유지가 가능해진다. 이를 통해 오래걸리는 렌더링을 중단할 수도 있게 된다.

그러면 어떻게 적용할 수 있을까?

 

 

2. useTransition

 

useTransition은 UI를 Blocking하지 않고 상태를 업데이트할 수 있는 리액트 훅이다.

useTransition은 두가지 항목을 반환하는데,

 

- isPending: 동시 렌더링이 진행 중이라면 ? true : false

- startTransition: 새로운 동시 렌더링을 발생 시키는 함수

 

 

const [isPending, startTransition] = useTransition()

 

 

구조분해 할당으로 각각을 가져올 수 있다.

 

그렇다면 어떻게 사용해야 할까? 우선 state를 업데이트할 때 non-blocking하게끔 하는 예제가 있었다.

 

 

export default function TabContainer() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('about');

  function selectTab(nextTab) {
    // 동시성을 적용할 부분
    startTransition(() => {
      setTab(nextTab); // 이 setState는 동시성을 가지고 업데이트될 것이다.
    });
  }

  return (
    <>
      <TabButton
        isActive={tab === 'about'}
        onClick={() => selectTab('about')}
      >
        About
      </TabButton>
      <TabButton
        isActive={tab === 'posts'}
        onClick={() => selectTab('posts')}
      >
        Posts (slow)
      </TabButton>
      <TabButton
        isActive={tab === 'contact'}
        onClick={() => selectTab('contact')}
      >
        Contact
      </TabButton>
      <hr />
      {tab === 'about' && <AboutTab />}
      {tab === 'posts' && <PostsTab />} {/* PostsTab은 렌더링에 시간이 오래 걸린다. */}
      {tab === 'contact' && <ContactTab />}
    </>
  );
}

 

 

react dev 에 아주 친절한 예제가 있었다. 

위 코드에서 setTab (tab을 update하는 setState) 는 동시성을 가지고 transition으로써 업데이트될 것이다.

따라서 setTab으로 인해 새로운 rendering이 일어날 때 다른 UI의 blocking이 방지가 된다.

이로 인해, 위 예제에서는

 

- posts 탭을 선택했을 경우 PostsTab 컴포넌트가 렌더링되는 동안 다른 tab (about or contact)를 선택하여 rendering을 중단할 수 있다.

- 부모 컴포넌트에서 setTab을 했을 경우에 자식 컴포넌트에 startTransition이 적용되어 있다면 마찬가지로 동시성을 가질 수 있다.

 

 

그리고 추가적으로 useTrasition을 사용할 때 알아야할 점이 있다.

 

- isPending을 사용하여 렌더링 완료 유무를 판단하는 UI를 조작할 수도 있다.

if (isPending) {
    return <b className="pending">{children}</b>;
 }

- Controlled input state는 transition에 사용할 수 없다.

// ❌ Can't use transitions for controlled input state
  startTransition(() => {
    setText(e.target.value);
  });

 

 

3. useDeferredValue

 

useDeferredValue는 위  "Controlled input state는 transition에 사용할 수 없다" 와 같은 경우에도 동시 업데이트를 실행하고 싶을 때 사용할 수 있다.

useDeferredValue는 UI의 일부를 업데이트하는 것을 연기하는 리액트 훅이다.

 

일단 예제를 보자. 아래처럼 선언할 수 있다.

 

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  // ...
}

 

동시성을 적용하고자 하는 state를 useDeferredValue 훅의 파라미터로 넣었다.

그러면 query의 변화가 Controlled Input으로 인해 일어난다고 하더라도 동시성이 적용된 query 값을 가질 수 있을 것이다.

 

 

4. Suspense

 

이전 Day 1 포스팅에도 잠깐 나왔던 내용이지만, Suspense로 감싸진 컴포넌트는 렌더링이 오래 걸릴 경우 Suspense 컴포넌트가 이를 기다리며 fallback props에 넣어준 JSX를 리턴해준다.

 

<Suspense fallback={<h2>Loading...</h2>}>
  <SearchResults query={deferredQuery} />
</Suspense>

 

SearchResults 컴포넌트의 렌더링이 느리다면 fallback으로 Loading...이 화면에 표시될 것이다.

동시성이라고 하긴 좀 애매하긴 하지만..

 

 

 

 

 

 

 

이로써 DAY2 - 리액트의 동시성 (React Concurrency) 학습을 마쳤다. 참고 자료는 아래를 참고!

https://velog.io/@eunbinn/react-concurrency
https://react.dev/reference/react/useTransition

 

DAY2까지 무사히 마쳐서 뿌듯! 프론트엔드 전문가로 계속해서 나아가자.

 

Comments