ReactJS - 컴포넌트 생명 주기

안녕하세요, 미래의 React 개발자 여러분! 오늘 우리는 React 컴포넌트의 생명 주기를 탐험하는 흥미로운 여정을 시작할 것입니다. 프로그래밍에 처음 접근하신 분이라도 걱정하지 마세요 - 저는 친절한 안내자로서 단계별로 모든 것을 설명해 드릴 것입니다. 이 튜토리얼이 끝나면 React 컴포넌트가 어떻게 탄생하고 성장하며, 결국 안녕을 인사하는지 잘 이해하실 수 있을 것입니다. 시작해 보겠습니다!

ReactJS - Component Life Cycle

컴포넌트 생명 주기는 무엇인가요?

코드로 들어가기 전에 '생명 주기'라는 것에 대해 이야기해 보겠습니다. 우리 인간은 탄생, 성장, 그리고... 아시는대로... 등 다양한 단계를 거칩니다. React 컴포넌트 역시 자신만의 생명 단계를 가지고 있습니다. 이 단계를 컴포넌트 생명 주기라고 부릅니다.

React 컴포넌트를 가상의 애완동물로 생각해 보세요. 처음 만들어지면 탄생합니다. 그런 다음 상호작용을 통해 성장하고 변화합니다. 마지막으로 더 이상 필요하지 않을 때 사라집니다. 이 생명 주기를 이해하는 것은 동적인 및 효율적인 React 애플리케이션을 만들기 위해 매우 중요합니다.

컴포넌트의 세 가지 주요 단계

React 컴포넌트는 세 가지 주요 단계를 거칩니다:

  1. 마운트(탄생)
  2. 업데이트(성장)
  3. 언마운트(안녕)

이 각 단계와 관련된 메서드를 탐구해 보겠습니다.

1. 마운트 단계

이는 우리의 컴포넌트가 탄생하고 DOM(문서 객체 모델 - 웹 페이지의 가족 트리라고 상상해 보세요)에 추가되는 시기입니다.

이 단계의 주요 메서드:

메서드 설명
constructor() 컴포넌트가 마운트되기 전에 호출되는 컴포넌트의 생성자
render() 실제로 컴포넌트를 렌더링하는 메서드
componentDidMount() 컴포넌트가 DOM에 마운트된 후 호출됨

간단한 예제를 보겠습니다:

import React, { Component } from 'react';

class BabyComponent extends Component {
constructor(props) {
super(props);
this.state = { message: "저는刚刚 태어났어요!" };
console.log("생성자: 안녕하세요, 저는 만들어지고 있어요!");
}

componentDidMount() {
console.log("componentDidMount: 저는 이제 DOM에 들어갔어요!");
}

render() {
console.log("렌더링: 저는 렌더링되고 있어요!");
return <h1>{this.state.message}</h1>;
}
}

export default BabyComponent;

이 예제에서, 우리의 BabyComponent는 탄생 단계를 거칩니다. constructor가 먼저 호출되어 초기 상태를 설정합니다. 그런 다음 render가 호출되어 실제 DOM 요소를 생성합니다. 마지막으로 componentDidMount가 호출되어 컴포넌트가 완전히 탄생하고 DOM에 추가되었음을 알립니다.

2. 업데이트 단계

이 단계는 컴포넌트의 상태가 변경되거나 새로운 props를 받을 때 발생합니다.

이 단계의 주요 메서드:

메서드 설명
shouldComponentUpdate() 컴포넌트가 다시 렌더링해야 하는지 결정
render() 업데이트된 데이터로 컴포넌트를 다시 렌더링
componentDidUpdate() 컴포넌트가 업데이트된 후 호출됨

이전 예제를 확장해 보겠습니다:

import React, { Component } from 'react';

class GrowingComponent extends Component {
constructor(props) {
super(props);
this.state = { age: 0 };
}

componentDidMount() {
this.ageInterval = setInterval(() => {
this.setState(prevState => ({ age: prevState.age + 1 }));
}, 1000);
}

shouldComponentUpdate(nextProps, nextState) {
return nextState.age % 5 === 0; // 5년에 한 번씩 업데이트
}

componentDidUpdate() {
console.log(`저는 이제 ${this.state.age} 살이에요!`);
}

render() {
return <h1>저는 {this.state.age} 살입니다</h1>;
}

componentWillUnmount() {
clearInterval(this.ageInterval);
}
}

export default GrowingComponent;

이 예제에서, 우리의 GrowingComponent는 매秒마다 "성장"합니다. shouldComponentUpdate 메서드는 5년에 한 번씩 생일을 축하합니다(케이크를 절약하려고요). 컴포넌트가 업데이트될 때마다 componentDidUpdate가 생일 메시지를 로그합니다.

3. 언마운트 단계

이는 컴포넌트가 DOM에서 제거되는 이별 단계입니다.

이 단계의 주요 메서드:

메서드 설명
componentWillUnmount() 컴포넌트가 언마운트되고 파괴되기 직전에 호출됨

위의 GrowingComponent 예제에서, 우리는 componentWillUnmount를 사용하여 인터벌을 清除하여 메모리 누수를 방지합니다.

생명 주기 API의 작동 예제

이제 모든 생명 주기 메서드를 하나의 더 복잡한 예제로 통합해 보겠습니다. 우리는 간단한 "기분 추적기" 애플리케이션을 만들어 보겠습니다.

import React, { Component } from 'react';

class MoodTracker extends Component {
constructor(props) {
super(props);
this.state = { mood: 'neutral', intensity: 5 };
console.log('생성자: 기분 추적기가 생성되고 있습니다');
}

componentDidMount() {
console.log('ComponentDidMount: 기분 추적기가 이제 DOM에 들어갔습니다');
this.moodInterval = setInterval(() => {
const moods = ['happy', 'sad', 'excited', 'nervous', 'neutral'];
const newMood = moods[Math.floor(Math.random() * moods.length)];
this.setState({ mood: newMood });
}, 5000);
}

shouldComponentUpdate(nextProps, nextState) {
return this.state.mood !== nextState.mood;
}

componentDidUpdate() {
console.log(`기분이 업데이트됨: ${this.state.mood}`);
}

componentWillUnmount() {
console.log('ComponentWillUnmount: 안녕 기분 추적기!');
clearInterval(this.moodInterval);
}

handleIntensityChange = (e) => {
this.setState({ intensity: e.target.value });
}

render() {
console.log('렌더링: 기분 추적기가 렌더링 중');
return (
<div>
<h1>현재 기분: {this.state.mood}</h1>
<input
type="range"
min="1"
max="10"
value={this.state.intensity}
onChange={this.handleIntensityChange}
/>
<p>강도: {this.state.intensity}</p>
</div>
);
}
}

export default MoodTracker;

MoodTracker 컴포넌트는 우리가 논의한 모든 생명 주기 메서드를 보여줍니다. 기분이 5초에 한 번씩 무작위로 변경되고, 사용자가 기분 강도를 조절할 수 있습니다.

지출 관리 애플리케이션에서 생명 주기 API 사용

이제 실제 애플리케이션에서 생명 주기 메서드를 어떻게 사용할 수 있는지 살펴보겠습니다. 예를 들어, 지출 관리 애플리케이션에서 사용할 수 있습니다.

import React, { Component } from 'react';

class ExpenseManager extends Component {
constructor(props) {
super(props);
this.state = { expenses: [], total: 0 };
}

componentDidMount() {
// API에서 지출 데이터를 가져오는 것을 시仿합니다
setTimeout(() => {
const fetchedExpenses = [
{ id: 1, description: '식료품', amount: 50 },
{ id: 2, description: '가스', amount: 30 },
{ id: 3, description: '영화 티켓', amount: 20 },
];
this.setState({ expenses: fetchedExpenses }, this.calculateTotal);
}, 1000);
}

shouldComponentUpdate(nextProps, nextState) {
// 총액이 변경되었을 때만 업데이트
return this.state.total !== nextState.total;
}

componentDidUpdate() {
console.log(`총 지출이 업데이트됨: $${this.state.total}`);
}

calculateTotal = () => {
const total = this.state.expenses.reduce((sum, expense) => sum + expense.amount, 0);
this.setState({ total });
}

render() {
return (
<div>
<h1>지출 관리</h1>
<ul>
{this.state.expenses.map(expense => (
<li key={expense.id}>{expense.description}: ${expense.amount}</li>
))}
</ul>
<h2>총액: ${this.state.total}</h2>
</div>
);
}
}

export default ExpenseManager;

ExpenseManager 컴포넌트에서:

  1. componentDidMount를 사용하여 컴포넌트가 처음 로드될 때 API에서 지출 데이터를 가져옵니다.
  2. shouldComponentUpdate는 총액이 변경되었을 때만 렌더링을 업데이트합니다.
  3. componentDidUpdate는 총액이 변경될 때마다 로그를 남깁니다.

이 생명 주기 메서드를 활용하면 효율적이고 반응성 있는 지출 추적 애플리케이션을 만들 수 있습니다.

컴포넌트 생명 주기를 이해하는 것은 효율적인 React 애플리케이션을 만들기 위해 매우 중요합니다. 이를 통해 특정 액션을 언제 발생시킬지, 성능을 최적화하고 자원을 효과적으로 관리할 수 있습니다. 이 개념을 계속 연습하면 곧 복잡하고 동적인 React 애플리케이션을 쉽게 만들 수 있을 것입니다!

Credits: Image by storyset