Activity 컴포넌트
작성일:2025.10.09|조회수:1

리액트로 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 접근 방식이 가진 한계를 뛰어넘는 새로운 상태 관리 모델이라고 할 수 있다.
더 읽어보기
2026.03.01
렌더링 전략 정리
리액트와 Next.js에서 렌더링 전략은 단순한 옵션 선택이 아니다. 이는 서비스의 초기 로딩 속도, 서버 비용, 캐싱 전략, SEO 노출, 개발 복잡도까지 동시에 좌우하는 아키텍처 결정이다. 프로젝트 규모가 커질수록 “어디에서 HTML을 생성하는가”, “언제 자바스크립트를 실행하는가”…
2026.02.22
View Transition API
웹 애플리케이션에서 전환 품질은 기능 완성도와 동등한 수준으로 중요하다. 사용자가 목록에서 항목을 선택해 상세 화면으로 이동할 때, 화면이 자연스럽게 이어지면 서비스는 빠르고 안정적으로 느껴진다. 반대로 동일한 기능이라도 전환이 끊기면 체감 성능과 신뢰도는 동시에 하락한다. 전환은 부가…
2025.10.08
useEffectEvent
React를 사용하면서 useEffect 안에서 상태를 참조할 때 의외로 자주 겪는 문제가 있다. 바로 stale closure 문제다. 예를 들어 어떤 값이 변경되었는데, useEffect 내부의 콜백에서는 여전히 이전 값을 읽고 있는 현상이다. 이는 React의 클로저 구조상 자연스…
2025.05.31
모달과 팝오버, 그리고 앵커 포지셔닝
모달과 팝오버를 칼로 베듯 명확히 구분해본 적은 없었다. 그저 화면 한가운데에 떠서 다른 작업을 막으면 모달, 특정 버튼을 눌렀을 때 앵커를 기준으로 나타나면 팝오버라는 정도로 느슨하게 생각해왔다. 하지만 두 개념은 단순한 위치나 동작의 차이를 넘어, 그 목적과 사용 방식에서 뚜렷한 차…
2026.04.11
Trie 자료구조
문자열 데이터를 다룰 때 단순히 “이 단어가 있나?”만으로는 부족한 순간이 있다. 자동 완성처럼 특정 접두사로 시작하는 후보를 모아야 할 때도 있고, 어떤 키에 값을 두고 빠르게 찾고 싶을 때도 있다. 이럴 때 가장 자연스럽게 떠올릴 수 있는 자료구조가 바로 Trie이다.Trie는 무엇…
2026.03.19
Streams API 부록 2. 왜 이미지는 위에서 아래로 나타날까
웹 페이지에서 이미지를 로드할 때 흥미로운 장면을 종종 볼 수 있다. 이미지가 한 번에 완전히 나타나는 것이 아니라, 위에서 아래로 조금씩 채워지면서 나타나는 경우가 있기 때문이다. 특히 네트워크가 느리거나 이미지가 큰 경우 이런 현상이 더 분명하게 보인다. 마치 이미지가 위쪽부터 스캔…
2026.03.19
Streams API 부록 1. HTTP 다운로드 진행률은 어떻게 계산될까
파일을 다운로드할 때 가끔 몇 퍼센트 진행되었는지 혹은 진행 막대(progress bar)가 조금씩 채워지는 모습을 볼 수 있다. 그런데 모든 다운로드가 이런 식으로 진행률을 보여 주는 것은 아니다. 어떤 다운로드는 퍼센트가 표시되지만, 어떤 경우에는 진행 막대 없이 로딩 스피너만 계속…
2026.03.13
Streams API 4. 왜 모든 언어에는 Stream API가 존재할까
Streams API를 공부하다 보면 묘한 기시감을 느끼게 된다. JavaScript에서 ReadableStream, WritableStream, TransformStream을 살펴보고 있는데, 어딘가 낯설지 않다. Java를 써 본 사람이라면 InputStream, OutputStre…
댓글
댓글을 불러오는 중...