main-logo

React18에서의 Concurrent, Transition

Concurrent, Transition에 대하여

profile
김현기
2023년 03월 24일 · 0 분 소요

들어가며

React18이 나온지 1년이 되어가는 것 같습니다. 현재 프로젝트에서 React18을 사용하고 있지는 않지만 18버전에서 React 팀이 중요하게 생각하는 업데이트 내용과 어떤 기능이 추가되었는지 소개하는 공식 블로그 글을 소개하면서 유용한 기능인 useTransition도 같이 소개하려고 합니다.

동시성

React18에서 가장 중요한 추가 기능은 동시성입니다. 동시성은 그 자체로 기능이 아닙니다. React가 동시에 여러 버전의 UI를 준비할 수 있도록 하는 새로운 배후 메커니즘입니다. (중략) React 개발자가 React 내부에서 동시성이 어떻게 작동하는지 궁금해 할 것이라고 기대하지는 않습니다. React에서 동시성은 핵심 렌더링 모델에 대한 기본 업데이트입니다. 따라서 동시성이 어떻게 작동하는지 아는것이 매우 중요하지는 않지만 높은 수준에서 동시성이 무엇인지 아는 것은 충분한 가치가 있습니다.

Concurrent React의 핵심 속성은 렌더링을 중단할 수 있다는 것입니다. React 18로 처음 업그레이드하면 동시 기능을 추가하기 전에 업데이트가 이전 버전의 React와 동일하게 중단되지 않는 단일 동기 트랜잭션으로 렌더링됩니다. 동기식 렌더링을 사용하면 업데이트가 렌더링을 시작하면 사용자가 화면에서 결과를 볼 수 있을 때까지 아무것도 중단할 수 없습니다.

동시 렌더링에서는 항상 그런 것은 아닙니다. React는 업데이트 렌더링을 시작하고 중간에 일시 중지한 다음 나중에 계속할 수 있습니다. 진행 중인 렌더링을 완전히 포기할 수도 있습니다. React는 렌더링이 중단되더라도 UI가 일관되게 표시되도록 보장합니다. 이를 위해 전체 트리가 평가되면 끝날 때까지 DOM 변형을 수행하기 위해 기다립니다. 이 기능을 통해 React는 메인 스레드를 차단하지 않고 백그라운드에서 새 화면을 준비할 수 있습니다. 이는 UI가 대규모 렌더링 작업 중에 있더라도 사용자 입력에 즉시 응답하여 유동적인 사용자 경험을 생성할 수 있음을 의미합니다.

위는 React18에서 동시성을 설명하는 글인데요 요약해보자면 React18에서는 사용자에게 보여지는 UI가 일관되게 보여지는 것을 유지하면서 백그라운드에서는 메인 스레드를 차단하지 않고 렌더링을 중지하거나 완전히 포기할 수 있는 기능이라고 합니다. useTransition을 통해 어떤 기능인지 정확히 알아볼까요?

Transition

전환(Transition)은 긴급 업데이트와 긴급하지 않은 업데이트를 구분하기 위한 React의 새로운 개념입니다.

  • 긴급 업데이트는 입력, 클릭, 누르기 등과 같은 직접적인 상호 작용을 반영합니다.
  • 전환 업데이트는 UI를 하나의 뷰에서 다른 뷰로 전환합니다.

타이핑, 클릭 또는 누르기와 같은 긴급 업데이트는 물리적 개체의 작동 방식에 대한 직관과 일치하도록 즉각적인 응답이 필요합니다. 그렇지 않으면 사용자들은 “틀렸다”고 생각합니다. 그러나 전환(transition)은 사용자가 화면에서 모든 값이 표시되기 전에 보여지는 중간값을 최종값으로 기대하지 않기 때문에 다릅니다.

일반적으로 최상의 사용자 경험을 위해서는 단일 사용자 입력으로 긴급 업데이트와 긴급하지 않은 업데이트가 모두 발생해야 합니다. 입력 이벤트 내에서 startTransition API를 사용하여 어떤 업데이트가 긴급하고 “전환(transition)“인지 React에 알릴 수 있습니다.

여기까지 블로그에 소개된 Transition에 관한 설명이었는데요 핵심은 최상의 사용자 경험을 위해서는 어떤 업데이트가 긴급한지 전환(transition)인지 아는게 필요하며 React18에서 이러한 기능을 제공한다는 것입니다. 예제를 구현해보면서 한번 더 이해해 볼까요?

예제(검색)

1만개의 더미 데이터가 있고 input value를 통해 filter를 사용하는 로직입니다.

이렇게 한번 1만개의 데이터 작업을 직접적으로 처리하지는 않겠지만 (페이지 네이션 이용, 혹은 서버측에서 필터 작업)

React18에서는 클라이언트 측에서 useTransition을 이용해 이런 사용자 경험을 동시성을 통해 완화할 수 있습니다.

예제에서 확인 할 수 있는 사용자 경험

예제가 code-sandbox로 구현되어서 완벽한 사용자 경험을 측정하기 위한 환경은 아니지만 검색창에 텍스트를 입력하거나 텍스트를 지우면 문제를 확인 할 수 있습니다.

1만개의 데이터를 실시간으로 filter 해주고 있기 때문에 렌더링 하는데 딜레이가 발생합니다.

우리가 현재 사용하고 있는 input에 텍스트를 입력하거나 텍스트를 지우기 위해 delete 버튼을 누르는 행위는 위에서 설명한 urgent update(긴급 업데이트)에 해당하는 액션이며 이런 액션에서의 딜레이 발생은 사용자에게 좋지 않은 사용자 경험을 제공합니다.

예제에서 개선할 수 있는 사용자 경험

위에서 설명한 Transition을 사용하여 React에게 어떤 업데이트가 urgent update인지 Transition인지 알려줄 수 있습니다.

예제에서 urgent update는 input에서 발생하는 액션이고 urgent update에 해당하지 않는 로직은 input에서 받은 값을 통해 filtering 해주는 로직입니다.

우리는 filtering 해주는 로직을 React에게 urgent update가 아니라고 알려주어야 합니다.

import { useTransition } from "react";
const [isPending, startTransition] = useTransition();

startTransition(() => {
  setFilterTerm(e.target.value);
});

isPending은 urgent update가 아니라고 정의한 로직이 여전히 실행 보류중 인지 아닌지 알려주는 boolean 값입니다. useTransition hook을 사용해야만 isPending 값을 얻을 수 있는데

만약 hook을 사용하지 않겠다고 하면 React에서 아래 처럼 startTransition만 import할 수 있습니다.

import { startTransition } from 'react';

startTransition(() => {
  setFilterTerm(e.target.value);
});

개선 확인

개선 전, 개선후 의 차이점

가장 큰 차이점은 input의 사용자 입력과 리스트의 업데이트가 동기화되지 않는다는 것입니다. React에게 filterTerm의 업데이트의 우선 순위가 낮다고 알려주고 input의 업데이트가 urgent update라고 알려주기 때문에 개선 전과는 다르게 리스트 업데이트의 속도가 input 보다는 지연되어 업데이트 됩니다.

이런 부분이 React18에서 이야기 하는 동시성의 한 부분입니다.

전환이 사용자에 의해 중단되면(예: 여러 문자를 연속으로 입력) React는 완료되지 않은 오래된 렌더링 작업을 제거하고 최신 업데이트만 렌더링합니다.

이는 글 초반에 나온

Concurrent React의 핵심 속성은 렌더링을 중단할 수 있다는 것입니다.에 내용에 해당합니다.

주의

하지만 사용자 경험을 위해 모든 부분에서 useTransition을 사용해야 할까요? 그렇지 않습니다.

특별하게 느린 로직에서 오래된 기기에 대응해야 하고 이런 문제를 해결할 방법이 없을때 사용해야 합니다. 위 예제는 useTransition의 사용 전과 후를 비교하기 위한

극단적인 예일 뿐이며 다른 솔루션을 통해서 충분히 해결할 수 있는 문제입니다. useMemo와 useCallback을 모든 부분에서 사용할 필요가 없는 것 처럼

useTransition 또한 React가 추가적으로 수행하는 로직 중 하나이므로 오히려 사용하지 않아야 될 곳에 모두 사용한다면 더 느려질 수 있습니다.

마치며

React18에서 주요 업데이트 내용인 동시성과 useTransition에 대해 살펴봤습니다. 이번 업데이트가 메이저한 업데이트는 아니였지만

사용자 경험을 개선하기 위한 노력을 느낄 수 있는 React 팀의 생각을 엿볼 수 있었습니다.

또한 되도록이면 예제는 code-sandbox가 아닌 로컬에서 직접 구동해 보시면서 진행하시길 부탁드립니다.

긴 글 읽어주셔서 감사합니다.

참고문서