bkdragon's log

Concurrent Rendering 본문

React

Concurrent Rendering

bkdragon 2023. 8. 24. 15:19

React 18 부터 적용되는 렌더링 방식이다. 동시성 렌더링을 통해 React는 렌더링 자체에 개입하여 이를 중단하거나 재개하거나 폐기할 수 있다.

 

React 18 이전에는 synchronous rendering (동기적 렌더링) 방식이였기 때문에 한번 렌더링 작업이 시작되면 중단할 수 없었고 이로 인해 우선순위가 높은 (빠른 응답이 필요한) 작업의 처리가 늦어지는 문제가 있었다.

 

동시성은 여러 작업을 작은 단위로 나눈 뒤, 우선순위를 정하고 그에 따라 작업을 번갈아 수행하는 방법이다. 실제로 동시에 이루어지는 것은 아니다. 다만 작업 간의 전환이 매우 빠르게 이루어지며 동시에 수행되는 것처럼 보인다.

 

React의 git discussion에서 좋은 이미지를 찾았다.

https://github.com/reactwg/react-18/discussions/46

전화 상황을 예로 들고 있는데, Alice와 통화 중 이를 잠시 보류하고 Bob과의 통화를 마치고 다시 Alice와 통화를 하고 마치는 예시이다. Bob의 전화가 우선순위가 높은 상황이였다고 생각할 수 있다.

 

 

가로선 (time 선) 이 두 줄 일 순 없을까? JavaScript가 싱글스레드이기 때문에 이는 불가능하다

 

 

우선순위는 어떻게 지정할 수 있을까? React 18에서 추가된 useTransition을 사용하면 된다!

useTransition은 두가지 값을 반환하는데 작업이 대기(지연)중임을 알리는 boolean값과 낮은 우선순위를 적용할 수 있는 함수이다.

const [isPending, startTransition] = useTransition();

const [count, setCount] = useState(0);
  
function onClick() {
  startTransition(() => {
    setCount(c => c + 1);
  })
}

이렇게 하면 Count 상태의 업데이트는 우선 순위가 낮아진다.

 

 

useTransition을 사용하면 Debounce와 Throttling의 한계를 극복할 수 있다. 이 둘은 어찌됐든 일정 시간을 기다려야한다. 그리고 무거운 작업이 계산될 때 상호작용이 멈춰버린다.

function App() {
  const [isPending, startTransition] = useTransition();
  const [searchTerm, setSearchTerm] = useState('');

  const products = getProducts(searchTerm);

  function onChangeSearchTerm (event : Event) {
    startTransition(() => {
      setSearchTerm(event.target.value);
    });
  }

  return (
    <div id="app">
      <input type="text" onChange={updateFilterHandler} />
      {isPending && <p>Updating List...</p>}
      <ProductList products={products} />
    </div>
  );
}

이런 코드가 있고 getProducts가 무거운 작업이면 searchTerm을 변경하는 setSearchTerm을 startTransition으로 감싸서 우선순위를 낮추면 input 창에 입력하는 상호작용이 막히지 않게된다. 그리고 products 값이 변경되기 이전의 UI 를 유지한다. 추가로 Loading UI을 보여주는 작업을 쉽게 할 수 있다

 

 

제어 컴포넌트에서는 transition이 동작하지 않는다.

const [text, setText] = useState('');
// ...
function handleChange(e) {
  startTransition(() => {
    setText(e.target.value);
  });
}
// ...
return <input value={text} onChange={handleChange} />;

이렇게 하면 input의 값이 동기적으로 업데이트되어야하기 때문에 transition이 동작하지 않는다.

 

 

이럴 때는 useDeferredValue를 사용해 실제 값보다 지연되는 상태를 만들어서 사용할 수 있다.

const deferredText = useDeferredValue(text);

return <input value={deferredText} onChange={handleChange} />;

 

 

공식문서에서는 “different universe”라는 표현을 사용한다. 상태가 업데이트 되기 이전의 UI와 이후의 UI가 동시에 존재하고 업데이트 준비가 끝나면 두 “universe”가 합쳐진다.

 

'React' 카테고리의 다른 글

[React Query] 쿼리 키 관리하기  (0) 2024.07.16
드래그를 활용한 그리드 아이템 크기 조절  (0) 2024.06.29
Streaming SSR  (0) 2023.08.21
Server Component  (0) 2023.08.21
useEffect 친해지기 1  (0) 2023.08.09