PUBLISHED
Activity 컴포넌트
작성일: 2025.10.09

리액트로 UI를 구성하다 보면, 특정 컴포넌트를 조건부로 렌더링해야 하는 상황이 자주 발생한다. 일반적으로는 삼항 연산자 등을 활용해서 컴포넌트를 렌더링하는데, 내 경우에는 이런 조건 렌더링을 반복적으로 작성하는 게 싫어 <Show>라는 특별한 컴포넌트를 만들어 사용하고 있다. 하지만 어떤 방식으로든 조건부 렌더링을 통해 컴포넌트를 DOM 트리에서 제거하면 근본적인 한계에 부딪힌다. 바로 컴포넌트가 언마운트(unmount)되면서 그 안에 있던 모든 내부 상태(state)가 함께 사라진다는 점이다. 예를 들어, 사용자가 탭을 잠시 다른 곳으로 옮겼다가 돌아왔을 때 이전에 입력하던 폼 데이터나 스크롤 위치가 모두 초기화되는 경험을 겪게 될 수 있다.
이러한 상태 유실 문제를 피하기 위해 많은 개발자들이 컴포넌트를 언마운트하는 대신 CSS의 display: none 속성으로 잠시 숨기는 방법을 선택하기도 한다. 이 방법은 컴포넌트가 계속 마운트된 상태를 유지하므로 상태를 완벽하게 보존할 수 있다는 확실한 장점이 있다. 그러나 이는 또 다른 문제를 야기하는데, 컴포넌트가 화면에 보이지 않을 뿐 백그라운드에서는 여전히 '살아있는' 상태로 모든 Effect를 계속 실행한다는 것이다. 내부의 useEffect는 계속해서 외부 API를 주기적으로 호출(polling)하거나 웹소켓 연결을 유지하는 등의 작업을 멈추지 않으며, 이는 결국 사용자가 보지도 않는 기능에 소중한 리소스를 낭비하는 결과를 낳게 된다.
바로 이 문제를 해결하기 위해 React 19.2 버전에서 Activity라는 새로운 기능이 도입되었다. Activity는 컴포넌트를 완전히 언마운트하지 않고도 비활성화된 상태로 전환할 수 있게 해주는 개념이다. 즉, DOM과 상태는 그대로 유지하되, 그 안의 Effect와 이벤트 핸들러는 일시적으로 중단된다. 쉽게 말해 Activity는 display: none과 unmount의 중간 지점에 있는 기능이다. 컴포넌트를 시각적으로 감추면서도 상태를 그대로 보존하고, 동시에 불필요한 Effect 실행을 막아 리소스를 절약할 수 있다.
아래의 예시는 Activity를 사용하여 탭 전환 시 상태를 보존하면서도, 보이지 않는 탭의 Effect는 자동으로 중단시키는 코드이다.
import { useState, useEffect } from 'react';
import { Activity } from 'react';
function ChatTab() {
useEffect(() => {
console.log('ChatTab active');
const interval = setInterval(() => {
console.log('🔵 Fetching messages...');
}, 2000);
return () => {
console.log('ChatTab paused');
clearInterval(interval);
};
}, []);
return <div>💬 Chat Messages</div>;
}
function ProfileTab() {
useEffect(() => {
console.log('ProfileTab active');
return () => console.log('ProfileTab paused');
}, []);
return <div>👤 User Profile</div>;
}
export default function TabView() {
const [activeTab, setActiveTab] = useState('chat');
return (
<div>
<button onClick={() => setActiveTab('chat')}>Chat</button>
<button onClick={() => setActiveTab('profile')}>Profile</button>
<Activity mode={activeTab === 'chat' ? 'visible' : 'hidden'}>
<ChatTab />
</Activity>
<Activity mode={activeTab === 'profile' ? 'visible' : 'hidden'}>
<ProfileTab />
</Activity>
</div>
);
}
이 코드를 실행하면, 사용자가 탭을 전환할 때 ChatTab과 ProfileTab의 useEffect가 자동으로 중단되거나 재개되는 것을 확인할 수 있다. React는 비활성화된 Activity를 DOM 트리 안에 그대로 유지하지만, 그 내부의 Effect와 이벤트 핸들러는 모두 일시 정지된 상태로 만든다. 다시 활성화되면 React는 기존 상태를 그대로 복원하고 필요한 Effect만 재실행한다.
이 구조 덕분에 Activity는 상태 보존과 부수 효과 제어, 그리고 성능 최적화까지 모두 만족시킬 수 있다. 기존의 조건부 렌더링이나 display: none 접근 방식이 가진 한계를 뛰어넘는 새로운 상태 관리 모델이라고 할 수 있다.