본문 바로가기

리액트

[React.v18 ] useTransition 과 논블로킹

반응형

 useTransition

useTransition 은 리액트 18 버전 이상에서 새롭게 등장한 훅으로, 논블로킹을 구현한 훅이다. 논블로킹이란  어떤 스레드의 실패 또는 중단이 다른 스레드의 실패 또는 중단을 유발하지 않도록 작업에 대한 요청은 순서대로 받아들이지만, 처리는 우선순위가 높게 책정된 즉,  우선 완료된 스레드 부터 이루어지도록 하는 것을 의미한다.

 

쉽게 말해, 논블로킹은 기존 작업의 실행이 후에 들어오는 작업의 실행을 막지  않는다. 반면에 블로킹은 이전 작업의 실행이 이후에 들어오는 작업의 실행을 막는다.

 

 

즉, 특정 스레드가 아무리 오래 걸린다고 하더라도, 다음 스레드의 작업이 먼저 끝난다면 다음 스레드의 작업 부터 우선 처리해주므로, 사용자가 오래 걸리는 작업을 하던 도중에도 다른 작업을 즉시 실행할 수 있다.

 

암튼, useTransition 을 사용한다면, 사용자는 하나의 작업을 처리하기 위해 오랜 시간 대기할 필요 없이 동시에 다른 작업을 수행할 수 있도록 해준다 라고 이해하면 될 것 같다(작업이 동시에 처리되는 병렬성과는 다른 개념이다. 병렬성은 말그대로 여러 작업을 병행하여 처리하는 것을 의미하지만, 논블로킹은 작업의 우선순위를 다르게 부여하여, 우선순위가 높은 작업부터 우선적으로 처리해 나가는 방식이다)

 

개념 정리 ) 논 블로킹이란 스레드 처리의 동시성을 극대화하는 방식으로, 작업이 오래 걸리는 일부 스레드의 업데이트를 지연시키고 그 시간 동안 다른 스레드 작업을 높은 우선순위로 두어서 처리하는 방식을 의미한다. 

 

사용법

컴포넌트의 최상단에 useTransition 을 import 해오고, 컴포넌트 함수 상단에 배열구조분해 할당을 사용하여, 다음과 같이 셋팅한다.

import { useState, useTransition } from 'react';

function TabContainer() {
  const [isPending, startTransition] = useTransition();
  // ...
}

 

 

여기서, isPending 은 현재 트랜잭션을 보류하고 있는지. 쉽게 말해 현재 처리중인 작업이 존재하는지를  boolean 값으로 나타낸다. 만일 작업 중이라면 true 을 반환하고, 작업이 없다면 false 를 반환한다. 이 값을 활용하면, 트랜잭션이 처리 중일 때 로딩 스피너 혹은 스켈레톤 등을 보여줄 수 있다.

 

startTransition 은 보통 작업 처리가 오래 걸려서 그 시간 동안 다른 작업이 블록될 수 있는 로직을 처리할 때 사용(쉽게 말해, 동기적으로 처리되는 함수 등이 들어감) 된다. 즉, 이 함수에 등록된 작업들은 각각 우선순위를 부여받게 되는데, 만일 하나의 작업이 처리되는데 많은 시간이 소요될 것으로 예측된다면, 해당 작업을 중지하고, 바로 처리 가능한 작업의 우선적으로 처리할 수 있도록 한다.

 

설명은 위와같이 했지만, 사실상 해당 함수의 콜백으로 등록된 함수는 낮은 우선순위를 가진다.

 

다음은 위 설명을 토대로 작성된 사용법 예시이다. 아래 코드에서 startTransition 의 콜백으로 등록된  setActiveTab 함수는 작업 처리에 있어서 낮은 우선순위를 가지게 된다. 따라서 해당 작업으로 인해 다른 작업의 처리가 블록되지 않으므로, 렌더링이 지연되더라도 사용자 입장에서는 부드럽고 자연스럽게 다른 작업을 요청하여 바로 처리할 수 있게 된다.

 

즉,  key 가 0 인 탭을 클릭 시 해당탭을 렌더링 하는데 오랜 시간이 걸리더라도, 바로 key 가 1에 해당하는 작업을 요청하여 즉시 처리할 수 있게 된다는 소리이다.

const App = () => {
  const [activeTab, setActiveTab] = useState(0);
  const [isPending, startTransition] = useTransition();

  const handleTabClick = (tabIndex) => {
    startTransition(() => {
      setActiveTab(tabIndex);
    });
  };

  return (
    <>
      <ul>
        <li key="0" onClick={() => handleTabClick(0)}>Tab 1</li>
        <li key="1" onClick={() => handleTabClick(1)}>Tab 2</li>
        <li key="2" onClick={() => handleTabClick(2)}>Tab 3</li>
      </ul>
      <div style={{ display: activeTab === 0 ? "block" : "none" }}>
        Tab 1 content
      </div>
      <div style={{ display: activeTab === 1 ? "block" : "none" }}>
        Tab 2 content
      </div>
      <div style={{ display: activeTab === 2 ? "block" : "none" }}>
        Tab 3 content
      </div>
    </>
  );
};

 

사용시 고려해야할 점

- useTransition 이 동기적인 처리를 보다 유연하고 자연스럽게 처리할 수 있도록 도움을 주지만, 너무 오래 걸리는 작업이 사용자에게 중요한 작업이라면 오히려 사용자에게 불쾌감을 형성할 수 있으므로 사용 시 주의가 필요하다.

앞서 언급 했듯이, 기존 작업 처리를 뒤로 미루고, 새로운 작업을 우선 처리하기 때문이다. 

 

- startTransition 에 전달되는 함수동기식으로 처리되는 함수여야 한다. chat gpt 를 사용해서 예시를 출력해보았는데, 아주 잘못된 예시를 맞는 예시인 것처럼 적어놔서 식겁했다. 공식문서에 떡하니 잘못된 방법이라고 나와있었는데 말이다. 

 

아래 코드는 비동기적 코드를 임의로 넣어 1초 동안 setPage 를 처리하는데 지연시간을 주고 있다. 이는 공식 문서의 예시인데 잘못된 방식이라고 한다.

startTransition(() => {
  // ❌ Setting state *after* startTransition call
  setTimeout(() => {
    setPage('/about');
  }, 1000);
});

 

이를 개선한 코드는 아래와 같다. 잘보면 startTransition 의 경우에는 트랜잭션을 유지하는 시간을 지정할 수 있는데, 두 번째 인자에 ms 를 기준으로 값을 전달하면 해당 시간 만큼 트랜잭션 실행시간을 지연시킨다.

setTimeout(() => {
  startTransition(() => {
    // ✅ Setting state *during* startTransition call
    setPage('/about');
  });
}, 1000);

 

이 외에도 useTransition 을 사용할 때 주의 및 고려해야 할 점이 많다. 무턱대고 좋아보인다고 사용했다가는 오히려 성능이나 사용성면에서 좋지 못한 결과를 초래할 수 있기 때문이다. 무엇보다 공식 문서를 잘 참고해서 맞게 사용하는 것이 옳다고 본다.

 

참고해야 하는 자료

https://react.dev/reference/react/useTransition

 

useTransition – React

The library for web and native user interfaces

react.dev

 

반응형