본문 바로가기
FrontEnd/React.js

[React 19] Activity 컴포넌트

by 위그든씨 2025. 12. 19.

React 19에서 추가된 <Activity>는 “컴포넌트를 언마운트하지 않고 숨기는” 용도로 쓰는 새 컴포넌트다.

탭/모달/사이드바처럼 자주 보였다 숨겼다 하는 UI에서 상태를 그대로 유지한 채 토글할 수 있게 해준다.

Activity 기본 개념

  • <Activity>는 자식 컴포넌트의 생명주기 경계를 만들어 주는 컴포넌트다.
  • mode="visible" | "hidden" | "auto" 같은 props로 Activity 안의 UI를 보이거나 숨길 수 있다.
  • 숨길 때도 컴포넌트를 언마운트하지 않고 DOM과 React state를 보존하는 것이 핵심이다.

상태가 유지되는 숨김 처리

  • 일반적인 조건부 렌더링({isOpen && <Panel />})은 false가 되면 컴포넌트가 언마운트되어 useState 등 로컬 상태가 사라진다.
  • <Activity mode="hidden">으로 감싸면, 화면에서는 display: none처럼 보이지 않지만 컴포넌트 인스턴스와 state는 유지된다.
  • 다시 visible로 바꾸면, 마치 일시 정지했다가 재생하듯 기존 상태 그대로 UI가 복귀한다.

DOM 상태까지 함께 보존

  • Activity 경계 안에서 숨길 때는 브라우저 레벨의 DOM 상태도 유지된다.
  • 예를 들어 입력 중이던 <input> 값, <textarea> 내용, 스크롤 위치 등이 숨김/보임 전환 사이에서도 유지된다.
  • 작성 중인 폼이 있는 탭 패널이나 멀티 스텝 폼 UI에서, 다른 탭으로 이동했다가 돌아와도 값이 초기화되지 않는 UX를 쉽게 만들 수 있다.
import { useState } from 'react';

export default function Sidebar() {
  const [isExpanded, setIsExpanded] = useState(false)
  
  return (
    <nav>
      <button onClick={() => setIsExpanded(!isExpanded)}>
        Overview
        <span className={`indicator ${isExpanded ? 'down' : 'right'}`}>
          &#9650;
        </span>
      </button>

      {isExpanded && (
        <ul>
          <li>Section 1</li>
          <li>Section 2</li>
          <li>Section 3</li>
        </ul>
      )}
    </nav>
  );
}

export default function App() {
  const [isShowingSidebar, setIsShowingSidebar] = useState(true);

  return (
    <>
      {isShowingSidebar && (
        <Sidebar />
      )}

      <main>
        <button onClick={() => setIsShowingSidebar(!isShowingSidebar)}>
          Toggle sidebar
        </button>
        <h1>Main content</h1>
      </main>
    </>
  );
}

기존에 state로 사이드바를 토글 했다면 Sidebar 자체가 DOM에서 사라지면서 다시 나타났을 땐 Sidebar 내부 state는 초기화가 되어있었음.

Activity로 관리한다면 

  • 숨길 때 해당 boundary 안의 DOM은 display: "none"으로 처리해서 화면과 레이아웃에서 제거
  • 동시에 그 안의 Effect들은 파괴(unmount)해서 백그라운드 사이드 이펙트를 막음
  • 하지만 컴포넌트의 state와 UI 스냅샷은 유지해서 다시 보이게 만들면 이전 상태 그대로 복원

즉 사이드바 안에서 관리 중인 state는 토글이 되든 말든 값을 계속 유지한다는 것

import { Activity, useState } from 'react';


export default function App() {
  const [isShowingSidebar, setIsShowingSidebar] = useState(true);

  return (
    <>
      <Activity mode={isShowingSidebar ? 'visible' : 'hidden'}>
        <Sidebar />
      </Activity>

      <main>
        <button onClick={() => setIsShowingSidebar(!isShowingSidebar)}>
          Toggle sidebar
        </button>
        <h1>Main content</h1>
      </main>
    </>
  );
}

미리 렌더링과 체감 속도 향상

  • 아직 사용자에게 보이지 않을 UI를 미리 렌더링해 두고 싶을 때도 <Activity>를 활용할 수 있다.
  • mode="hidden" 상태로 렌더링해 두면, 화면에는 나오지 않지만 컴포넌트가 이미 준비되고 Suspense 기반 데이터 로딩도 진행된다.
  • 사용자가 탭을 전환하거나 모달을 열 때 이미 로딩이 끝나 있어서, 로딩 스피너 없이 바로 UI를 보여줄 수 있어 체감 성능이 좋아진다.

Hydration 및 상호작용 성능 최적화

  • 서버 렌더링(SSR)과 Hydration 환경에서 <Activity>는 UI 트리를 나누는 경계로 활용할 수 있다.
  • 상호작용이 중요한 버튼, 네비게이션 등은 먼저 hydrate하고, 덩치 큰 UI는 Activity 경계 안에서 나중에 활성화되도록 조절할 수 있다.
  • 이 방식은 초기 페이지 로딩 시 “클릭은 바로 먹히는데, 나머지 무거운 뷰는 뒤에서 준비되는” 구조를 만들 때 유용하다.

언제 쓰면 좋은가

  • 탭 패널, 아코디언, 사이드바, 모달처럼 자주 토글되지만 상태 초기화가 싫은 UI.
  • 긴 폼, 에디터, 멀티 스텝 UI처럼 입력 값과 스크롤을 그대로 유지해야 하는 화면.
  • 사용자 행동에 앞서 화면을 미리 준비해 두고, 전환 시 로딩 없이 “즉시 표시”를 원하는 경우.
  • SSR + Hydration 환경에서 주요 인터랙션을 먼저 활성화하고, 덩치 큰 UI를 점진적으로 활성화하고 싶은 경우.