리액트의 동시성! (useTransition)

김인태·2025년 5월 11일
3
post-thumbnail

🐶 개요

길었던 연휴 많이 놀았기도하고, 지금 하고 있는 항해에서 보내준 글들을 읽고, 블로그 올리는 챌린지 같은 것을 진행하는데.. 저는 솔직히 글쓰는 재주는 없어서 챌린지에 참여하기 보다는 글 중에서 평소에 관심있던
주제가 있어서 한 번 알아보고자 글을 씁니다! 아래는 오프코치님이 보내주셨던 리액트의 동시성에 관한 링크입니다.

[코드 한 줄로 경험하는 React 동시성]
https://tech.remember.co.kr/%EC%BD%94%EB%93%9C-%ED%95%9C-%EC%A4%84%EB%A1%9C-%EA%B2%BD%ED%97%98%ED%95%98%EB%8A%94-react-%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%98-%EB%A7%88%EB%B2%95-5ff18aee148d

위 글에서는 사용자가 검색창에 키워드를 입력할 때마다 실시간으로 UI를 업데이트하는 기능을 개발했고, 입력할 때마다 수천개의 데이터가 필터링되는 과정에서 멈칫거리는 현상이 나타나 React 18의 동시성 모드에서 useTranstion API 를 적용해보았더니 해결됐다고합니다. 저는 생각했습니다. 그러면 그냥 debounce나 throttle로 해결하면 되는거 아닌가 ?.? 라구요. 하지만 이런 방식은 지연시간을 임의로 설정해야해 사용자의 입력속도를 예측해야 하는 한계가 있다! 라고 합니다. React 팀은 이를 위해 인간의 인지와 기대에 관한 연구를 했고 useTransition PR에서 확인할 수 있습니다!
그리고 useTransition의 설명!
https://ko.react.dev/reference/react/useTransition

PR 내용을 요약하자면

PR내용요약

React에서 상태 업데이트 시 화면이 멈추거나 응답성이 떨어지는 문제가 생겼고, 특히 입력 필드 업데이트와 같은 상호작용에서 느린 렌더링이 사용자 경험을 저하시켰다!
그래서 사용자 입력에 대한 즉각적인 반응성을 보장하기 위해서 이 훅을 만들었고, 화면이 완전히 멈추는 대신 중요한 상호작용이 계속 가능하도록 했다!
그래서 동시성 모드의 철학을 실현시키고자 했다!
렌더링 작업을 작은 단위로 나누어 브라우저가 중간에 이벤트를 처리할 수 있게 하고, 비동기 렌더링을 통해 더 복잡한 UI에서도 부드러운 사용자 경험 제공하려고 했다!
왜냐하면 사용자들은 타이핑, 클릭, 터치와 같은 행위에서 즉각적인 반응을 기대하고, 그렇지 않으면 작동하지 않다고 느끼기 때문이다!

🐷그래서 동시성이 뭔데?

저는 간단하게 요약할꺼지만, 더 깊은 내용이 궁금하시다면! 제가 참고했던 블로그를 꼭 들어가서 확인해보세요! 그래서 동시성이 뭐냐?! 아래 내용들을 같이 확인해보면서 알아가보죠!

단일 스레드의 한계

JavaScript는 본질적으로 단일 스레드 언어입니다. 브라우저에서는 다음 작업들이 모두 같은 스레드(메인 스레드)에서 실행됩니다

  • JavaScript 코드 실행
  • DOM 업데이트
  • 레이아웃 계산
  • 페인팅(화면 그리기)
  • 사용자 이벤트 처리

이로 인해 발생하는 문제:

  • 하나의 작업이 오래 걸리면 다른 모든 작업이 차단됨
  • 복잡한 React 렌더링 작업이 시작되면 사용자 입력 처리가 지연됨
    => 이것이 "UI 프리징" 또는 "쟁크(jank)"라 불리는 현상

React 16 이전의 동기식 렌더링

사용자 이벤트 → 상태 업데이트 → 렌더링 시작 → 모든 컴포넌트 렌더링 완료 → DOM 업데이트 → 브라우저 페인팅

이 과정에서 중간에 중단할 수 없었기 때문에, 큰 업데이트가 있으면 UI가 멈추는 현상이 발생!

그래서 동시성?

동시성(Concurrency)은 "여러 작업을 동시에 진행하는 것처럼 보이게 하는 능력"입니다. JavaScript는 실제로 단일 스레드지만, React는 작업을 잘게 쪼개고 적절히 스케줄링하여 마치 여러 작업이 동시에 진행되는 것처럼 만듭니다.

병렬성 vs 동시성
병렬성 : 실제로 여러 작업을 동시에 실행 (멀티코어/멀티스레드)
동시성 : 업들 사이를 빠르게 전환하며 동시에 진행되는 것처럼 보이게 함

동시성을 활용한 React 18 렌더링

상태 변경 → 우선순위 할당 → 작업 단위로 렌더링 + 중단/재개 → DOM 업데이트

다양한 우선순위로 여러 상태 업데이트를 동시에 처리하고, 중요한 업데이트는 덜 중요한 업데이트를 중단 가능하다! 그렇다면 동시성의 우선순위는 어떤 기준으로 정해지는걸까?

Lane 모델

React의 Lane 모델은 업데이트 우선순위를 관리하는 시스템으로, 도로의 차선처럼 각 업데이트가 중요도에 따라 다른 "차선"에 배치됩니다. 이 모델은 32비트 정수를 사용해 구현되어 있으며, 각 비트가 하나의 차선을 나타냅니다. 오른쪽에 있는 낮은 비트 위치일수록 우선순위가 높습니다.
비트 단위로 설계한 이유는 비트연산이 컴퓨터 아키텍처의 가장 기본적인 수준에서 직접 처리되기 때문에 CPU가 효율적으로 처리할 수 있고, 단일 32비트 정수 하나로, 최대 31개의 서로 다른 우선순위 레벨을 표현할 수 있어 메모리 사용량을 최소화 할 수 있습니다.

우선순위는 어떻게 정해지나!

React에 세 가지 우선순위 시스템이 있습니다

  1. Lane 우선순위 : 업데이트의 중요도를 나타냄
  2. 이벤트 우선순위 : 사용자의 이벤트의 중요도를 나타냄
  3. 스케줄러 우선순위 : 스케줄러에서 작업 예약 시 사용되는 우선순위.

이 세 가지 우선순위 시스템은 서로 연결되어 있으며, Lane 우선순위를 이벤트 우선순위로 변환하고, 다시 스케줄러 우선순위로 매핑하여 작업을 처리합니다.

Lane 우선순위 결정 방식

const NoLanes = 0b0000000000000000000000000000000;
const SyncLane = 0b0000000000000000000000000000001;  // 최우선
const InputContinuousLane = 0b0000000000000000000000000000100;
const DefaultLane = 0b0000000000000000000000000010000;
const TransitionLanes = 0b0000000001111111111111111000000;  // 여러 비트 할당
const IdleLane = 0b0100000000000000000000000000000;  // 최저 우선순위

먼저 이벤트 우선순위가 특정 레인값에 매핑됩니다.
https://github.com/facebook/react/blob/f9d78089c6ec8dce3a11cdf135d6d27b7a8dc1c5/packages/react-reconciler/src/ReactEventPriorities.js#L24C1-L28C58

Lane 우선순위가 결정되는 핵심 요소들

  1. 업데이트의 출처:
  • 직접 호출된 setState는 SyncLane을 받음
  • startTransition 내부의 업데이트는 TransitionLane을 받음
  • useEffect 내부의 업데이트는 DefaultLane을 받음
  1. React 내부 함수가 Lane을 할당하는 방식
// 단순화된 예시
function requestUpdateLane(fiber) {
  // 현재 실행 컨텍스트에 따라 레인 결정
  if (executionContext & RenderContext) {
    return SyncLane;
  }
  
  if (currentEventTransitionLane !== NoLane) {
    return currentEventTransitionLane;
  }
  
  // 현재 이벤트 우선순위에 따라 레인 결정
  const eventLane = getCurrentEventPriority();
  return eventLane;
}

이벤트 우선순위 결정 과정

  1. DOM 이벤트와 우선순위 매핑
function getEventPriority(domEventName) {
  switch (domEventName) {
    // 개별적이고 즉각적인 반응이 필요한 이벤트
    case 'click':
    case 'keydown':
    case 'keyup':
      return DiscreteEventPriority;
    
    // 연속적인 처리가 필요한 이벤트
    case 'drag':
    case 'mousemove':
    case 'scroll':
      return ContinuousEventPriority;
    
    // 기타 일반 이벤트
    default:
      return DefaultEventPriority;
  }
}
  1. 이벤트처리 시 우선순위 전파
  • React는 이벤트가 발생할 때 해당 이벤트의 우선순위를 내부 컨텍스트에 저장
  • 이벤트 핸들러 내에서 발생하는 업데이트는 이 우선순위를 상속
  1. 우선순위 변환로직
// 레인에서 이벤트 우선순위로 변환
export function lanesToEventPriority(lanes) {
  const lane = getHighestPriorityLane(lanes);
  
  // 높은 우선순위부터 검사하여 해당하는 이벤트 우선순위 반환
  if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
    return DiscreteEventPriority;  // 최우선
  }
  if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
    return ContinuousEventPriority;
  }
  if (includesNonIdleWork(lane)) {
    return DefaultEventPriority;
  }
  return IdleEventPriority;  // 최저 우선순위
}

스케줄러 우선순위로의 최종변환

스케쥴러 우선순위는 React가 작업을 수행하기 위해 내부 스케줄러에 작업을 등록할 때 사용합니다.

  1. 우선순위 변환 매핑 :
// 이벤트 우선순위를 스케줄러 우선순위로 변환
switch (lanesToEventPriority(nextLanes)) {
  case DiscreteEventPriority:
    schedulerPriorityLevel = ImmediateSchedulerPriority;  // 우선순위 1
    break;
  case ContinuousEventPriority:
    schedulerPriorityLevel = UserBlockingSchedulerPriority;  // 우선순위 2
    break;
  case DefaultEventPriority:
    schedulerPriorityLevel = NormalSchedulerPriority;  // 우선순위 3
    break;
  case IdleEventPriority:
    schedulerPriorityLevel = IdleSchedulerPriority;  // 우선순위 5
    break;
  default:
    schedulerPriorityLevel = NormalSchedulerPriority;  // 기본값
}
  1. 스케줄러 우선순위의 실제 영향
  • ImmediateSchedulerPriority: 동기적으로 즉시 실행
  • UserBlockingSchedulerPriority: 높은 우선순위로 비동기 실행 (250ms 타임아웃)
  • NormalSchedulerPriority: 일반 우선순위로 비동기 실행 (5000ms 타임아웃)
  • LowSchedulerPriority: 낮은 우선순위 (10000ms 타임아웃)
  • IdleSchedulerPriority: 브라우저 유휴 시간에만 실행
  1. 실제 스케줄링 매커니즘
// 단순화된 예시
function scheduleCallback(priorityLevel, callback) {
  let expirationTime;
  switch (priorityLevel) {
    case ImmediateSchedulerPriority:
      // 동기적 실행, 타임아웃 없음
      return Scheduler_scheduleCallback(
        Scheduler_ImmediatePriority,
        callback
      );
    case UserBlockingSchedulerPriority:
      // 짧은 타임아웃 (250ms)
      return Scheduler_scheduleCallback(
        Scheduler_UserBlockingPriority,
        callback
      );
    // ... 기타 우선순위 케이스
  }
}

이러한 메커니즘을 통해 React는 단일 스레드 환경에서도 작업을 중요도에 따라 처리하여, 마치 여러 스레드가 동시에 작업하는 것처럼 사용자 경험을 최적화합니다. 중요한 작업(사용자 입력)은 즉시 처리하고, 덜 중요한 작업(데이터 계산, 렌더링)은 필요에 따라 미루거나 중단함으로써 UI의 응답성을 유지합니다.

실습?!

useTransition API를 사용해서 한 번 실습해볼까요?

링크 : http://concurrency-test-in-react.vercel.app/
github 링크 : https://github.com/dlsxody1/concurrency-test-in-react

가벼운 실습이니, 컴포넌트는 잘게 나누지 않겠습니다!

동시성을 적용한 컴포넌트와 그렇지 않은(기존의 방법처럼 debounce를 적용한) 컴포넌트의 비교를 위해서
유저 리스트와 검색창, 그리고 동시에 CPU 부하를 일으키기위해서 유저 ID에서 소수를 판별하는 페이지를 만들어보겠습니다.

내용은 위의 링크를 확인해주시면 감사하겠습니다.

Debouncing vs useTransition

Debouncing

src/components/SearchBox.tsx를 봐주세요!
많이 익숙한 코드 패턴이죠?? 검색 입력과 같이 자주 발생하는 이벤트를 처리할 때 이런 패턴으로 많이 사용하곤 하죠
이 패턴은 사용자가 타이핑을 멈춘 후 300ms가 지난 후에만 검색 함수를 호출합니다.
사실 제가 느끼기에는 300ms 의 멈춤.. 크게 버벅거린다고 생각하지 않습니다만, 이것보다 더 부드러운 ux를
제공할 수 있다면 무조건 더 좋은 선택을 하겠죠?!


const SearchBox: React.FC<SearchBoxProps> = ({ onSearch }) => {
  const [inputValue, setInputValue] = useState("");
  const [debouncedValue, setDebouncedValue] = useState("");
  
  // 디바운스 효과 (타이핑 중 과도한 렌더링 방지)
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(inputValue);
    }, 300);

    return () => {
      clearTimeout(timer);
    };
  }, [inputValue]);

  // 디바운스된 값으로 검색 실행
  useEffect(() => {
    onSearch(debouncedValue);
  }, [debouncedValue, onSearch]);
  );
};

export default SearchBox;

useTransition

src/App.tsx를 봐주세요!
useTransition은 디바운싱과는 근본적으로 다른 접근 방식입니다. 위에서 보셨듯이 디바운싱은 이벤트 발생 빈도를 제한해서 리스트를 불러오는 함수 호출을 막고 있습니다. 반면 useTransition은 상태 업데이트 자체에 우선순위를 부여합니다.
startTransition으로 래핑된 상태 업데이트는 전환(transition) 으로 표시되어 낮은 우선순위로 처리됩니다.
이는 상태 업데이트가 즉시 적용되는 것이 아니라 더 중요한 업데이트(예 : 타이핑 응답)가 먼저 처리된 후에 적용됨을 의미합니다.

const [query, setQuery] = useState<string>("");
  //useTransition은 어떠한 매개변수도 받지 않음!
  //isPending 플래그는 대기중인 Transition이 있는지 알려줌
  //startTransition 함수는 상태 업데이트를 Transition으로 표시할 수 있음.
  
  const [isPending, startTransition] = useTransition();
  const [concurrencyEnabled, setConcurrencyEnabled] = useState<boolean>(true);
  const [isProcessing, setIsProcessing] = useState<boolean>(false);

//useCallback은 디바운스 효과로 인해 debouncedValue가 변경될 때마다 onSearch가 호출되고
//App 컴포넌트에서 그것이 다시 startTransition으로 처리되어 검색창에 아무것도 입력하지 않아도 
//초기값인 빈 문자열("")로 인해 계속 렌더링이 발생하여 추가하였습니다.

  const handleSearch = useCallback(
    (searchQuery: string): void => {
      // 이전 쿼리와 같다면 상태 업데이트 방지
      if (query === searchQuery) return;

      if (concurrencyEnabled) {
        startTransition(() => {
          setQuery(searchQuery);
        });
      } else {
        setQuery(searchQuery);
      }
    },
    [concurrencyEnabled, query]
  );

이제 이론은 어느정도 맛봤으니, 고마 테스트 해볼까요?

Test!

일단 테스트를 하려면, 빌드 후에 프로덕션 모드로 프로젝트를 실행시켜봅시다!
프로덕션 모드로 실행하는 이유는
1. 실제 사용자가 경험하는 환경과 가장 유사한 환경에서 측정하기 위함
2. 개발 모드에는 추가적인 경고나 에러체크, 디버깅 도구가 활성화 되어있어 정확한 성능측정이 어려움.

그러면 이제 프로덕션 모드에서 실행해보죠~!
저는 vite 를 사용해서
아래 명령어를 순서대로 입력해서 프로덕션 모드를 실행해보세요!

npm run build
npm run preview

총 테스트 시나리오는 2개로 진행하겠습니다.

빠른 타이핑 테스트 -> 연속적인 입력 상황에서 각 모드의 응답성 측정하기 CPU 부하 버튼을 클릭한 후에 "김이박최정강조" 글자를 1초에 2글자 속도로 빠르게 입력 후에 결과 측정

고부하 검색 테스트 -> 많은 결과를 반환하는 결과를 검색해서 2초 대기후 결과 측정

측정 지표는
FPS (Frames Per Second): UI 응답성 측정
총 차단 시간 (Total Blocking Time): 메인 스레드 차단 정도 측정
Long Tasks (50ms 이상): 긴 작업 횟수
입력 지연(Input Delay): 키 입력부터 반응까지의 지연 시간

위 4개의 지표를 가지고 점수를 매겨보도록하죠~!

좀 더 느리게 결과를 지켜보고 싶어서

cpu 감속과 네트워크는 3g로 해서 테스트를 진행해보겠습니다!

1. 빠른 타이핑 테스트

순서대로 일반모드 / 디바운스 모드 / 동시성 모드

1-a 일반 모드의 Performance 결과!

메인 스레드 활동 패턴

FPS: 평균 ~30 FPS로, 그래프에서 자주 프레임 드롭이 발생 (이미지의 상단 노란색 그래프의 낮은 구간)
Long Tasks: 약 15-20개의 50ms 이상 작업이 발견됨 (이미지에서 노란색/녹색의 긴 블록들)
최대 Task 지속시간: 입력마다 약 200-300ms의 Long Task 발생 (이미지에서 가장 긴 노란색 블록)
UI 프리징: 입력마다 발생하는 긴 작업으로 인해 약 8-10회 발생 (FPS 드롭 구간과 일치)

이미지에서 볼 수 있듯이 각 키보드 입력("타이머 실행" 블록) 후 즉시 긴 처리 작업이 이어지며, 이 때마다 FPS가 크게 하락합니다! 총 차단 시간은 여러 입력에 걸쳐 누적되어 약 10.5초에 달합니다

1-b 디바운스 모드의 performance 결과!

디바운스 모드의 Performance 패널 데이터는 다음과 같은 특징을 보여줍니다!

FPS: 평균 ~45 FPS로, 타이핑 중에는 안정적이나 필터링 중 일시적 하락 (노란색 FPS 그래프)
Long Tasks: 1-2개의 긴 작업만 발생 (36,000ms~48,000ms 구간에서 규칙적인 패턴)
최대 Task 지속시간: 약 1.46초의 단일 블록으로 처리 (Image 1의 긴 녹색/노란색 블록)
입력 지연: ~30-50ms로 타이핑 반응성 좋음 (키보드 이벤트와 처리 사이 간격이 좁음)

이미지에서 볼 수 있듯이 "타이머 실행"과 "함수 호출" 블록이 일정 간격으로 규칙적으로 배치되어 있으며, 입력이 끝난 후 단일 대형 작업 블록이 발생합니다.
디바운스의 FPS 그래프는 40,000ms~50,000ms 구간에서 상대적으로 안정적인 높은 값을 보여주지만, 필터링 작업 중 일시적 하락이 관찰됩니다.

1-c 동시성 모드 분석

FPS: 평균 55-60 FPS로 매우 안정적 (Image 4의 60,000ms70,000ms 구간에서 높고 일정한 노란색 그래프)
Long Tasks: 3-4개의 짧고 분산된 작업으로 처리 (56,000ms~68,000ms 구간에서 작은 녹색 블록들)
최대 Task 지속시간: "1.46초(자체 11μs)"로 표시된 작업이 여러 작은 청크로 분할됨
입력 지연: ~10-20ms로 매우 낮음 (키보드 이벤트와 처리 사이 간격이 매우 좁음)

이미지에서 볼 수 있듯이 녹색 블록이 여러 작은 청크로 분산되어 있으며, "타이머 실행" 블록이 짧고 여러 번 발생합니다.
FPS 그래프는 60,000ms~70,000ms 구간에서 매우 높고 안정적인 값을 보여주며, 필터링 작업 중에도 큰 하락이 없습니다!

2. 고부하 검색테스트


제공된 Performance 패널 그래프는 많은 결과를 반환하는 검색어를 입력하고 2초 대기 후 측정한 데이터로, 세 가지 모드(일반, 디바운스, 동시성)의 성능을 보여줍니다.

2-a 일반 모드의 performance 결과!

FPS: 평균 25-30 FPS로, 검색 결과가 로딩될 때 심각한 FPS 하락 발생 (상단 노란색 그래프에서 5,000ms45,000ms 구간에 여러 차례 드롭 발생)
Long Tasks: 다수의 긴 작업이 발생 (그래프 중앙 10,000ms15,000ms, 25,000ms30,000ms, 40,000ms~45,000ms 구간의 노란색 블록들)
작업 패턴: "타이머 실행", "함수 호출", "렌더링" 작업이 검색 후 집중적으로 메인 스레드를 차단 (하단부 빨간색과 노란색 블록 패턴)
메모리 사용: 긴 녹색 수직 막대(하단부)는 많은 DOM 노드와 메모리가 사용됨을 보여줌
UI 응답성: 검색 처리 중 여러 구간에서 UI가 완전히 멈춤 (FPS 그래프 하락 구간)

이미지에서 볼 수 있듯이 높은 부하의 검색 작업이 여러 긴 작업 블록으로 처리되며, 각 블록마다 메인 스레드를 완전히 차단하여 사용자 경험을 저하시킵니다. 대량의 데이터를 한 번에 처리하려는 시도로 인해 5,000ms10,000ms, 20,000ms25,000ms, 35,000ms~40,000ms 구간에서 높은 FPS를 유지하다가도 계산 집중 구간에서 급격한 FPS 하락이 관찰됩니다!

2-b 디바운스 모드의 performance 결과!

FPS: 평균 ~40-45 FPS로, 검색 시작 직후 한 번의 큰 FPS 하락 후 회복 (상단 노란색 그래프의 ~15,000ms 구간에서 큰 하락 발생)
Long Tasks: 1-2개의 집중된 긴 작업만 발생 (그래프 중앙 ~15,000ms 구간의 노란색 및 녹색 블록)
작업 패턴: 디바운스 타이머 종료 후 한 번에 모든 계산이 수행됨 (15,000ms 근처의 집중된 "타이머 실행" 및 "함수 호출" 블록)
결과 처리 효율성: 하나의 큰 작업 블록 이후 안정적인 FPS 유지 (그래프 20,000ms 이후 안정적인 높은 FPS)
메모리 효율성: 일관된 패턴의 녹색 수직 막대는 효율적인 메모리 사용을 나타냄

이미지에서 볼 수 있듯이 디바운스 모드는 검색어 입력이 완료된 후 한 번의 큰 작업으로 모든 필터링을 처리합니다. 이로 인해 ~15,000ms 부근에서 FPS가 크게 하락하지만, 그 이후에는 안정적인 높은 FPS를 유지합니다. 대기 시간(2초) 이후 한 번의 집중적인 계산이 이루어진 후 UI가 안정화되는 패턴이 뚜렷하게 관찰됩니다!

2-c 동시성 모드의 performance 결과!

동시성 모드의 Performance 패널 데이터는 다음과 같은 특징을 보여줍니다!

FPS: 평균 ~50-55 FPS로 매우 안정적 (상단 노란색 그래프가 전체 구간에서 높고 일정한 값 유지)
Long Tasks: 여러 개의 짧은 작업으로 분산 처리 (그래프 전체에 걸쳐 작은 녹색 블록들이 고르게 분포)
작업 패턴: "타이머 실행"과 "함수 호출" 작업이 여러 작은 청크로 분할되어 메인 스레드를 짧게 사용 (작은 노란색과 녹색 블록들)
병렬 처리: 여러 작업이 동시에 진행되며 서로 방해하지 않음 (25,000ms, 40,000ms 구간에서 보이는 병렬 패턴)
UI 응답성: 검색 처리 중에도 UI가 계속 반응 (FPS 그래프가 전체적으로 안정적)

이미지에서 볼 수 있듯이 동시성 모드는 대량의 검색 결과를 처리하는 고부하 작업을 여러 작은 청크로 나누어 처리합니다. 이로 인해 FPS가 전체 구간에서 안정적으로 유지되며, 사용자는 결과가 계산되는 동안에도 UI와 상호작용할 수 있습니다. 특히 25,000ms와 40,000ms 구간에서 보이는 바와 같이, 여러 작업이 병렬로 처리되면서도 메인 스레드를 장시간 차단하지 않는 효율적인 처리 패턴이 관찰됩니다!

😎결론!

  1. 성능 최적화는 단순한 속도가 아닌 사용자 경험에 관한 것이다!
    성능 테스트를 통해 가장 중요한 교훈은 단순히 '빠른' 코드보다 '체감적으로 부드러운' 코드가 사용자 경험에 더 중요하다는 점입니다. 일반 모드는 계산 시간 자체는 빠를 수 있지만, 메인 스레드를 차단하여 UI가 멈추는 현상이 발생합니다. 반면 동시성 모드는 전체 계산 시간이 약간 더 길어질 수 있어도 UI 반응성을 유지하여 사용자에게 더 나은 경험을 제공합니다.

  2. 최적화 기법은 트레이드오프를 수반한다!
    각 최적화 기법은 장단점이 있습니다 ->
    일반 모드: 구현 간단, 즉각적인 피드백 ↔ UI 차단, 불필요한 계산
    디바운스 모드: 리소스 효율성, 간단한 구현 ↔ 결과 표시 지연, 일시적 UI 차단
    동시성 모드: 우수한 UI 응답성, 점진적 업데이트 ↔ 구현 복잡성, 약간 높은 메모리 사용
    이상적인 솔루션은 애플리케이션의 요구사항과 제약 조건에 따라 달라집니다. 때로는 이러한 기법들을 결합하는 것이 최선일 수 있습니다.

  1. 측정 가능한 데이터를 기반으로 결정해야 한다!
    Performance 패널을 통한 객관적인 측정은 "느낌"보다 훨씬 신뢰할 수 있는 정보를 제공합니다. FPS, Long Tasks, 입력 지연 등의 지표를 통해 성능 문제의 원인을 정확히 파악하고 효과적인 해결책을 선택할 수 있습니다. 성능 최적화는 추측이 아닌 데이터를 기반으로 이루어져야 합니다.

  2. React의 패러다임 변화를 이해해야 더 나은 서비스를 제공할 수 있겠다!
    React 18의 동시성 기능은 단순한 API 추가가 아닌 근본적인 렌더링 패러다임의 변화를 의미하는 것이고, 그냥 신기술이 나와서 사용해보는 것이 아니라 패러다임의 변화를 이해하고, 모든 상황에서 적용할 수 있는 만능 해결책이 아니기 때문에 문제 상황과 요구사항에 맞게 해결할 수 있는 개발자가 되어보자!

profile
새로운 걸 배우는 것을 좋아하는 프론트엔드 개발자입니다!

0개의 댓글