React의 컴포넌트 생명주기(Lifecycle)는 컴포넌트가 생성(mount), 업데이트(update), 제거(unmount)될 때 React가 호출하는 일련의 메서드입니다.
컴포넌트 생명주기를 이해하면 컴포넌트가 언제 렌더링되는지, 언제 데이터를 불러올지, 언제 자원을 정리할지 등을 명확하게 이해하고 관리할 수 있습니다.
✅ 생명주기(Lifecycle)란?
컴포넌트가 DOM에 나타나고(mount), 업데이트되고(update), 사라질 때(unmount), React는 특정 메서드를 호출합니다.
크게 세 가지 단계로 나뉩니다:
- Mounting (생성)
- Updating (업데이트)
- Unmounting (제거)
📌 클래스 컴포넌트에서의 생명주기 메서드
함수 컴포넌트는 Hooks(useEffect)로 관리하며, 클래스 컴포넌트에서만 lifecycle 메서드를 직접 사용합니다.
React 컴포넌트의 대표적인 생명주기 메서드는 다음과 같습니다:
- Mounting(생성 및 추가) :
constructor()
,render()
,componentDidMount()
- Updating(업데이트) :
shouldComponentUpdate()
,componentDidUpdate()
- Unmounting(제거) :
componentWillUnmount()
🔷 각 생명주기 단계의 동작 원리
📌 1. Mounting (생성 단계)
컴포넌트가 처음 DOM에 추가될 때 실행되는 단계입니다.
순서는 다음과 같습니다:
- constructor
- getDerivedStateFromProps
- render
- componentDidMount
- 보통 초기 상태 설정, API 호출, 외부 데이터 가져오기를 합니다.
1class MyComponent extends React.Component { 2 constructor(props) { 3 super(props); 4 this.state = { count: 0 }; // 상태 초기화 5 } 6 7 static getDerivedStateFromProps(props, state) { 8 // 상태 초기화 또는 변경 시 props에 따라 업데이트 9 return null; // 변경 없으면 null 반환 10 } 11 12 componentDidMount() { 13 // DOM 생성 후 호출되는 메서드 (API 요청 등 초기화 작업) 14 console.log('Component mounted!'); 15 } 16 17 render() { 18 return <h1>{this.state.count}</h1>; 19 } 20}
📌 2. Updating (업데이트 단계)
컴포넌트의 상태(state)나 props가 변경될 때 실행됩니다.
순서는 다음과 같습니다:
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
1shouldComponentUpdate(nextProps, nextState) { 2 // 업데이트 여부 결정 3 return nextState.count !== this.state.count; 4} 5 6componentDidUpdate(prevProps, prevState, snapshot) { 7 // DOM 업데이트 이후 호출됨 8 console.log('Component updated!'); 9}
- shouldComponentUpdate에서 false를 리턴하면 업데이트를 막을 수 있습니다.
- componentDidUpdate는 DOM 업데이트 후 호출되어, 업데이트 후 처리(예: 데이터 재요청)에 사용됩니다.
📌 3. Unmounting (제거 단계)
컴포넌트가 DOM에서 제거될 때 호출되는 메서드입니다.
이 단계는 메서드가 하나뿐입니다:
- componentWillUnmount
1componentWillUnmount() { 2 // 이벤트 리스너 정리, 타이머 제거 등 리소스 정리 3 console.log('Component unmounted!'); 4}
📌 생명주기 메서드 사용 예시 코드
다음은 각 단계를 명확하게 이해할 수 있도록 작성한 클래스형 컴포넌트 예시입니다.
1import React from 'react'; 2 3class LifecycleExample extends React.Component { 4 constructor(props) { 5 super(props); 6 this.state = { count: 0 }; 7 console.log('constructor'); 8 } 9 10 static getDerivedStateFromProps(props, state) { 11 console.log('getDerivedStateFromProps'); 12 return null; 13 } 14 15 componentDidMount() { 16 console.log('componentDidMount'); 17 } 18 19 shouldComponentUpdate(nextProps, nextState) { 20 console.log('shouldComponentUpdate'); 21 return true; // 항상 업데이트 22 } 23 24 getSnapshotBeforeUpdate(prevProps, prevState) { 25 console.log('getSnapshotBeforeUpdate'); 26 return null; // 스냅샷 값 반환 가능 27 } 28 29 componentDidUpdate(prevProps, prevState, snapshot) { 30 console.log('componentDidUpdate'); 31 } 32 33 componentWillUnmount() { 34 console.log('componentWillUnmount'); 35 } 36 37 increment = () => { 38 this.setState({ count: this.state.count + 1 }); 39 }; 40 41 render() { 42 console.log('render'); 43 return ( 44 <div> 45 <p>Count: {this.state.count}</p> 46 <button onClick={() => this.setState({ count: this.state.count + 1 })}> 47 증가 48 </button> 49 </div> 50 ); 51 } 52} 53 54export default LifecycleExample;
로그 순서 예시:
constructor
getDerivedStateFromProps
render
componentDidMount
(상태 변경 시)
getDerivedStateFromProps
shouldComponentUpdate
render
getSnapshotBeforeUpdate
componentDidUpdate
📌 Hooks로 구현 시 (함수형 컴포넌트)
최근 React는 함수형 컴포넌트와 훅(useEffect)을 사용합니다.
클래스 컴포넌트의 생명주기는 함수 컴포넌트에서 다음과 같이 표현됩니다:
1import React, { useState, useEffect } from 'react'; 2 3function LifecycleExample() { 4 const [count, setCount] = useState(0); 5 6 // Mount (componentDidMount) 7 useEffect(() => { 8 console.log('Component Mounted'); 9 10 // Unmount 단계에서 실행될 정리함수 반환 11 return () => { 12 console.log('Component will unmount'); 13 }; 14 }, []); 15 16 // count가 변경될 때마다 실행 (componentDidUpdate와 유사) 17 useEffect(() => { 18 console.log('Component updated:', count); 19 }, [count]); 20 21 return ( 22 <div> 23 <h1>Count: {count}</h1> 24 <button onClick={() => setCount(count + 1)}>증가</button> 25 </div> 26 ); 27}
📌 정리
React 컴포넌트는 Mount, Update, Unmount 단계에 따라 특정 메서드를 호출하여 동작합니다.
- Mount 단계에서는 초기화 작업과 데이터 로드를 수행합니다.
- Update 단계에서는 UI가 최신 상태로 유지되도록 업데이트 여부를 결정합니다.
- Unmount 단계에서는 리소스를 정리하고 메모리 누수를 방지합니다.
생명주기 메서드 및 훅(useEffect)을 적절히 활용하면 React 컴포넌트가 효율적으로 관리됩니다.
SNS 서비스를 예로 들어 React의 컴포넌트 생명주기(Lifecycle) 원리를 쉽게 설명해 보겠습니다.
아래는 SNS에서 흔히 볼 수 있는 피드(Feed) 컴포넌트를 예로 들어 생명주기 메서드를 설명한 것입니다.
✅ SNS 앱의 피드 컴포넌트 생명주기 예시
SNS 앱을 생각하면, 다음과 같은 과정이 일어납니다:
- 처음 피드를 로딩할 때 (Mount)
- 사용자가 새 게시물을 작성하거나 스크롤하여 데이터를 더 불러올 때 (Update)
- 다른 화면으로 이동하거나 앱을 종료할 때 (Unmount)
🔷 1단계: Mount (피드 컴포넌트 생성)
피드 컴포넌트가 처음 화면에 나타나는 시점입니다.
이때 필요한 작업:
- 서버에서 최신 게시물 목록 가져오기 (API 호출)
- 초기 화면 설정 (데이터가 오기 전 로딩 화면)
📌 Mount 단계에서의 주요 메서드
- constructor
- 상태(state) 초기 설정 (게시물 목록, 로딩 상태 등)
- render
- 컴포넌트의 초기 UI 렌더링 (로딩 화면 등)
- componentDidMount
- 서버에서 실제 게시물 데이터를 받아옴 (API 요청)
▶️ 예제 코드
1class FeedComponent extends React.Component { 2 constructor(props) { 3 super(props); 4 this.state = { 5 posts: [], // 게시물 목록 초기화 6 loading: true, // 로딩 상태 관리 7 }; 8 } 9 10 componentDidMount() { 11 // 컴포넌트가 화면에 등장한 후 데이터 요청 12 fetch('/api/posts') 13 .then(res => res.json()) 14 .then(data => this.setState({ posts: data, loading: false })); 15 } 16 17 render() { 18 if (this.state.loading) { 19 return <div>Loading...</div>; 20 } 21 return ( 22 <div> 23 {this.state.posts.map(post => ( 24 <Post key={post.id} data={post} /> 25 ))} 26 </div> 27 ); 28 } 29}
🔷 2단계: Update (피드 컴포넌트 업데이트)
사용자가 피드에서 새로운 게시물을 작성하거나, 스크롤하여 다음 게시물 목록을 불러올 때 이 단계가 발생합니다.
이때 필요한 작업:
- 업데이트된 상태(state)에 따라 다시 렌더링
- 필요 시 서버에서 추가 데이터를 요청
📌 Update 단계에서의 주요 메서드
- shouldComponentUpdate
- 불필요한 렌더링을 막기 위해, 실제 변경이 있을 때만 업데이트 결정
- render
- 업데이트된 상태를 화면에 다시 그립니다.
- componentDidUpdate
- 데이터 변경이 발생하면 추가 작업(API 재요청 등)을 수행합니다.
▶️ 예제 코드 (새로운 게시물 추가)
1componentDidUpdate(prevProps, prevState) { 2 if (prevState.posts.length !== this.state.posts.length) { 3 console.log('새로운 게시물이 추가되었습니다.'); 4 } 5} 6 7handleNewPost = (newPost) => { 8 this.setState(prevState => ({ 9 posts: [newPost, ...prevState.posts], 10 })); 11};
이 과정에서 사용자가 새 게시물을 올리면 상태(state.posts)가 변경되고,
컴포넌트는 자동으로 **render() → componentDidUpdate()**를 실행하여 UI를 갱신합니다.
🔷 3단계: Unmount (피드 컴포넌트 제거)
사용자가 다른 화면(예: 프로필, 설정)으로 이동하거나 앱을 종료할 때, 피드 컴포넌트가 화면에서 제거되는 단계입니다.
이때 필요한 작업:
- 타이머, 이벤트 리스너 제거
- 불필요한 요청 취소 (메모리 누수 방지)
📌 Unmount 단계에서의 주요 메서드
- componentWillUnmount
- 이벤트 리스너 및 리소스 정리
▶️ 예제 코드 (이벤트 리스너 제거)
1componentDidMount() { 2 window.addEventListener('scroll', this.handleScroll); 3} 4 5componentWillUnmount() { 6 window.removeEventListener('scroll', this.handleScroll); 7 console.log('피드 컴포넌트가 화면에서 제거됨.'); 8} 9 10handleScroll = () => { 11 // 무한스크롤 처리 로직 12};
- 피드의 무한 스크롤 기능을 구현할 때, 컴포넌트가 제거되면 반드시 이벤트 리스너를 정리하여 메모리 누수를 막아야 합니다.
📌 함수형 컴포넌트에서의 구현 (useEffect 활용)
최근에는 함수형 컴포넌트를 사용하는 경우가 많습니다.
함수형 컴포넌트의 생명주기 관리에는 useEffect 훅을 사용합니다.
▶️ 함수형 예제 (Mount, Update, Unmount 모두 처리)
1import React, { useState, useEffect } from 'react'; 2 3function FeedComponent() { 4 const [posts, setPosts] = useState([]); 5 const [loading, setLoading] = useState(true); 6 7 useEffect(() => { 8 // Mount (생성 단계) 9 fetch('/api/posts') 10 .then(res => res.json()) 11 .then(data => { 12 setPosts(data); 13 setLoading(false); 14 }); 15 16 // Unmount (제거 단계) 17 return () => { 18 console.log('Feed 컴포넌트 제거됨'); 19 }; 20 }, []); // 빈 배열([])은 마운트와 언마운트 때만 실행됨 21 22 useEffect(() => { 23 // Update (업데이트 단계) 24 console.log('게시물 목록이 업데이트됨:', posts.length); 25 }, [posts]); // posts 상태가 변경될 때만 실행됨 26 27 if (loading) { 28 return <div>Loading...</div>; 29 } 30 31 return ( 32 <div> 33 {posts.map(post => ( 34 <Post key={post.id} data={post} /> 35 ))} 36 </div> 37 ); 38}
- Mount 시 데이터 로딩
- 게시물 목록(posts)이 변경될 때마다 업데이트 로직 실행
- 컴포넌트 제거 시(Unmount) 리소스 정리