컴포넌트 라이프사이클 이해하기 (함수형 컴포넌트 중심)
React 컴포넌트의 라이프사이클은 컴포넌트가 생성되고, 업데이트되며, 제거되는 과정을 말합니다.
함수형 컴포넌트에서는 useEffect 훅을 사용하여 이러한 라이프사이클 이벤트를 처리합니다.
이 절에서는 함수형 컴포넌트에서 라이프사이클을 다루는 방법과 클래스형 컴포넌트와의 차이점을 알아보겠습니다.
useEffect 훅 소개
useEffect 훅은 함수형 컴포넌트에서 부수 효과(side effects)를 수행할 수 있게 해줍니다.
이는 데이터 가져오기, 구독 설정, 수동으로 DOM 조작하기 등의 작업을 포함합니다.
기본 구조
useEffect(() => {
// 부수 효과를 수행하는 코드
return () => {
// 정리(clean-up) 함수 (선택사항)
};
}, [dependencies]);
컴포넌트 마운트 시 작업 수행
컴포넌트가 처음 렌더링될 때 특정 작업을 수행하려면 useEffect를 다음과 같이 사용합니다.
import React, { useEffect, useState } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
// 컴포넌트 마운트 시 데이터 가져오기
fetch('https://api.example.com/data')
.then(response => response.json())
.then(result => setData(result));
}, []); // 빈 의존성 배열은 이 효과가 마운트 시에만 실행됨을 의미합니다
return (
<div>
{data ? <p>{JSON.stringify(data)}</p> : <p>Loading...</p>}
</div>
);
}
이 예제에서 useEffect는 컴포넌트가 마운트될 때 한 번만 실행됩니다.
컴포넌트 업데이트 시 작업 수행
특정 값이 변경될 때마다 작업을 수행하려면 useEffect의 의존성 배열에 해당 값을 포함시킵니다.
import React, { useEffect, useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
// count가 변경될 때마다 실행
document.title = `You clicked ${count} times`;
}, [count]); // count를 의존성 배열에 포함
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
이 예제에서는 count 값이 변경될 때마다 문서의 제목이 업데이트됩니다.
컴포넌트 언마운트 시 정리 작업
컴포넌트가 언마운트되거나 재렌더링되기 전에 정리 작업을 수행해야 할 때는 useEffect에서 정리 함수를 반환합니다.
import React, { useEffect, useState } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
}, 1000);
// 정리 함수
return () => {
clearInterval(intervalId);
};
}, []); // 빈 의존성 배열은 이 효과가 마운트와 언마운트 시에만 실행됨을 의미합니다
return <p>Timer: {seconds} seconds</p>;
}
이 예제에서 정리 함수는 컴포넌트가 언마운트될 때 인터벌을 정리합니다.
클래스형 컴포넌트와의 차이점
클래스형 컴포넌트에서는 componentDidMount, componentDidUpdate, componentWillUnmount 등의 라이프사이클 메서드를 사용했습니다.
함수형 컴포넌트에서는 이러한 메서드들이 useEffect 하나로 대체됩니다.
클래스형 컴포넌트 예제
class ExampleComponent extends React.Component {
componentDidMount() {
console.log('Component mounted');
}
componentDidUpdate(prevProps, prevState) {
if (this.props.someProp !== prevProps.someProp) {
console.log('someProp changed');
}
}
componentWillUnmount() {
console.log('Component will unmount');
}
render() {
return <div>Example Component</div>;
}
}
동등한 함수형 컴포넌트
function ExampleComponent({ someProp }) {
useEffect(() => {
console.log('Component mounted');
return () => {
console.log('Component will unmount');
};
}, []); // 마운트와 언마운트 시에만 실행
useEffect(() => {
console.log('someProp changed');
}, [someProp]); // someProp이 변경될 때마다 실행
return <div>Example Component</div>;
}
함수형 컴포넌트에서 다루는 것의 장점
- 간결성: 여러 라이프사이클 메서드 대신 하나의 useEffect 훅으로 여러 상황을 처리할 수 있습니다.
- 관심사의 분리: 연관된 로직을 하나의 useEffect 안에 모을 수 있어, 코드의 구조화가 용이합니다.
- 재사용성: 커스텀 훅을 만들어 라이프사이클 관련 로직을 쉽게 재사용할 수 있습니다.
- 테스트 용이성: 순수 함수로 작성된 컴포넌트는 테스트하기가 더 쉽습니다.
- 성능 최적화: React의 동시성 모드(Concurrent Mode)와 더 잘 동작합니다.
useEffect 사용 시 주의사항
- 무한 루프 방지: 의존성 배열을 올바르게 설정하여 불필요한 재실행을 방지해야 합니다.
- 정리 함수의 중요성: 구독, 타이머 등의 리소스는 반드시 정리해야 합니다.
- 조건부 효과: useEffect 내부에서 조건문을 사용할 수 있지만, useEffect 자체를 조건부로 실행해서는 안 됩니다.
// 잘못된 사용
if (condition) {
useEffect(() => {
// ...
}, []);
}
// 올바른 사용
useEffect(() => {
if (condition) {
// ...
}
}, [condition]);
함수형 컴포넌트와 useEffect 훅을 사용하여 React 컴포넌트의 라이프사이클을 관리하는 것은 더 선언적이고 유연한 방식으로 부수 효과를 다룰 수 있게 해줍니다.
클래스형 컴포넌트의 라이프사이클 메서드에 비해 더 직관적이고 재사용 가능한 코드를 작성할 수 있으며, React의 최신 기능과도 더 잘 호환됩니다.
그러나 useEffect를 올바르게 사용하기 위해서는 의존성 배열의 올바른 사용, 정리 함수의 중요성, 그리고 조건부 실행에 대한 이해가 필요합니다. 이러한 개념을 잘 이해하고 적용한다면, 더 효율적이고 유지보수가 쉬운 React 애플리케이션을 개발할 수 있습니다.