두 컴포넌트를 비교하는 이유는 관심사의 분리 때문입니다.
유지보수성과 코드의 가독성을 높이기 위해 UI와 데이터 로직을 분리하는 것이 좋습니다.
프레젠테이셔널 컴포넌트 (Presentational Component)
UI를 렌더링하는 데 초점을 둔 컴포넌트입니다. 상태를 가지지 않으며, 주로 부모로부터 전달받은 props를 사용해 데이터를 보여줍니다.
컨테이너 컴포넌트 (Container Component)
상태를 관리하거나, 데이터를 가져오거나, 비즈니스 로직을 처리하는 컴포넌트입니다. 프레젠테이셔널 컴포넌트를 포함하며 데이터를 전달합니다.
// 프레젠테이셔널 컴포넌트
const UserCard = ({ name, email }) => (
<div>
<h2>{name}</h2>
<p>{email}</p>
</div>
);
// 컨테이너 컴포넌트
import React, { useState, useEffect } from 'react';
const UserContainer = () => {
const [user, setUser] = useState(null);
useEffect(() => {
// 가상의 API 호출
setTimeout(() => {
setUser({ name: 'John Doe', email: 'joey@xxxx.xxx' });
}, 1000);
}, []);
if (!user) return <p>Loading...</p>;
return <UserCard name={user.name} email={user.email} />;
};
클래스 컴포넌트는 JavaScript의 클래스 문법을 기반으로 작성됩니다. 상태 관리와 라이프사이클 메서드(componentDidMount, componentDidUpdate, componentWillUnmount 등)를 활용합니다.
그러나 클래스 컴포넌트의 한계는 명확합니다.
1. 복잡한 코드
라이프사이클 메서드에 여러 로직이 섞이기 쉽습니다. 예: componentDidMount에 초기화, API 호출, 이벤트 리스너 등록 등이 한꺼번에 들어갑니다.
2. this 바인딩 문제
클래스 내부 메서드에서 this가 컴포넌트 인스턴스를 가리키도록 바인딩해야 합니다.
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
// this 바인딩
this.increment = this.increment.bind(this);
}
increment() {
this.setState((prevState) => ({ count: prevState.count + 1 }));
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
위 코드의 this.increment는 반드시 bind를 통해 컴포넌트 인스턴스를 가리키도록 해야 합니다.
React Hooks는 함수 컴포넌트에서도 상태 관리 및 라이프사이클과 같은 기능을 사용할 수 있도록 합니다. React Hooks는 함수 컴포넌트를 중심으로 React를 설계하도록 장려합니다.
1. useState
상태를 관리합니다.
2. useEffect
컴포넌트의 부수효과(예: 데이터 가져오기, 이벤트 등록)를 처리합니다.
3. useContext
컨텍스트 API와 함께 사용하여 전역 상태를 관리합니다.
4. useReducer
복잡한 상태 관리를 처리할 수 있습니다.
5. useMemo & useCallback
성능 최적화를 위해 값이나 함수를 메모이제이션합니다.
import React, { useState, useEffect } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Count updated: ${count}`);
return () => {
console.log('Cleanup on unmount');
};
}, [count]); // count가 변경될 때마다 실행
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
1. 코드 간결화
클래스 컴포넌트보다 짧고 이해하기 쉬운 코드 작성이 가능합니다.
2. 재사용성 향상
커스텀 훅을 만들어 로직을 재사용할 수 있습니다.
3. this 문제 해결
함수 컴포넌트에는 this가 없으므로 바인딩 문제가 사라집니다.
import React, { useState, useEffect } from 'react';
// 커스텀 훅
const useFetch = (url) => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((response) => response.json())
.then(setData);
}, [url]);
return data;
};
// 컴포넌트에서 사용
const App = () => {
const data = useFetch('https://joey.xxxx.xxx/posts/1');
if (!data) return <p>Loading...</p>;
return (
<div>
<h1>{data.title}</h1>
<p>{data.body}</p>
</div>
);
};
현재 React 생태계에서는 함수 컴포넌트와 Hooks가 표준으로 자리 잡았습니다.
클래스 컴포넌트는 기존 프로젝트에서 여전히 사용되지만, 새로운 기능이 함수 컴포넌트와 Hooks 중심으로 설계되면서 점점 사용 빈도가 줄어들고 있습니다.
1. 새로운 기능 중심
React의 최신 기능(예: useState, useEffect, useContext 등)은 모두 Hooks를 기반으로 설계되었습니다.
Hooks는 함수 컴포넌트에서만 사용할 수 있기 때문에 최신 React 개발에서는 클래스 컴포넌트로 접근할 수 없는 기능이 많아졌습니다.
// 클래스 컴포넌트에서 Context 사용
class ThemeButton extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{(theme) => <button style={{ background: theme }}>Click Me</button>}
</ThemeContext.Consumer>
);
}
}
// 함수 컴포넌트에서 Context 사용
const ThemeButton = () => {
const theme = React.useContext(ThemeContext);
return <button style={{ background: theme }}>Click Me</button>;
};
Hooks 도입 후, Context와 같은 기능을 훨씬 간결하게 사용할 수 있습니다.
2. 간단한 코드 구조
함수 컴포넌트는 클래스 구문이나 this 바인딩이 필요하지 않아 코드가 더 단순하고 직관적입니다.
이는 특히 초보자에게 React를 학습하거나 코드베이스를 유지보수하는 데 큰 장점으로 작용합니다.
저는 공부를 하는 도중 그런 의문이 들었습니다. 왜..? 왜 함수형 컴포넌트가 더 간단할까? 코드를 쓸 때 느낌으로만 느꼈지 근본적인 이유에 대해서 생각을 안해본듯하여, 이번에 생각을 해보았습니다.
그래서 왜? 왜 함수형 컴포넌트가 편한데?
클래스 컴포넌트는 상태 관리 시 this.state와 this.setState()를 사용해야 하며, 메서드를 정의할 때 this를 바인딩해야 합니다.
함수 컴포넌트는 단순히 useState 훅을 통해 상태를 관리하고, 바인딩 문제 없이 함수를 정의할 수 있습니다.
// 클래스 컴포넌트
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState((prevState) => ({ count: prevState.count + 1 }));
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
// 함수 컴포넌트
const Counter = () => {
const [count, setCount] = React.useState(0); // useState로 상태 관리
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
차이점
클래스 컴포넌트: 상태 정의 시 constructor가 필요하며, this 바인딩과 setState 사용.
함수 컴포넌트: 상태 정의가 간단하며, this 관련 문제가 없음.
코드 길이: 함수 컴포넌트는 훨씬 간결하며, render() 메서드가 필요하지 않습니다.
직관성 : 클래스 컴포넌트에서는 this.props.name처럼 this를 사용해야 하지만, 함수 컴포넌트에서는 구조 분해 할당({ name })을 통해 더 직관적으로 사용할 수 있습니다.