이것은 매우 중요한 기술 인터뷰 질문입니다. 표면적으로 보기에는 두 가지 방식으로 컴포넌트를 작성하는 것뿐이지만, 그 차이점은 단순한 문법 차이를 훨씬 넘어섭니다. 아래에서 표면적 차이, 본질적 차이, 그리고 설계 철학 세 가지 측면에서 심층적으로 분석해 보겠습니다.
1. 표면적 차이 (문법 및 기본 사용법)
| 특성 | 클래스 컴포넌트 | 함수형 컴포넌트 |
|---|---|---|
| 문법 | ES6 Class, React.Component 상속 |
JavaScript 함수 |
| 상태 | this.state와 this.setState() 사용 |
useState Hook 사용 |
| 라이프사이클 | componentDidMount, componentDidUpdate 등 사용 |
useEffect Hook 사용 |
this 키워드 |
this 바인딩 문제 처리 필요 |
this 없음 |
| 성능 | 인스턴스 전체로 인해 상대적으로 무거움 | 가벼움 |
코드 예시 비교:
// 클래스 컴포넌트
class UserCounter extends React.Component {
constructor(props) {
super(props);
this.state = { value: 0 };
// this 바인딩 필요
this.incrementValue = this.incrementValue.bind(this);
}
incrementValue() {
this.setState({ value: this.state.value + 1 });
}
componentDidMount() {
console.log('컴포넌트 마운트됨');
}
render() {
return <button onClick={this.incrementValue}>값: {this.state.value}</button>;
}
}
// 함수형 컴포넌트
function FunctionalCounter() {
const [value, setValue] = useState(0);
useEffect(() => {
console.log('컴포넌트 마운트됨');
}, []);
const incrementValue = () => {
setValue(value + 1);
};
return <button onClick={incrementValue}>값: {value}</button>;
}
2. 본질적 차이 (근본적인 차이점)
이것이 두 컴포넌트의 차이를 이해하는 핵심입니다.
1. 사고 모델 및 코드 구성
- 클래스 컴포넌트: 라이프사이클 중심 & 관심사 분리 불량
- 코드는 "시점"을 중심으로 구성됩니다. "컴포넌트가 마운트된 후(
componentDidMount) 무엇을 해야 할까? 업데이트 후(componentDidUpdate) 무엇을 해야 할까? 언마운트 전(componentWillUnmount) 무엇을 해야 할까?"와 같은 고민이 필요합니다. - 문제점: 하나의 기능과 관련된 코드(예: 데이터 구독 및 구독 취소)가 다른 라이프사이클 메서드로 분할되며, 관련 없는 코드들은 하나의 메서드에 모여있을 수 있습니다.
class UserProfile extends Component {
componentDidMount() {
// 관련 없는 로직 A
document.title = `현재 점수: ${this.state.score}`;
// 관련 없는 로직 B
NotificationAPI.subscribeToUpdates(this.props.userId, this.handleNotification);
}
componentDidUpdate(prevProps) {
// 로직 A의 업데이트 부분
if (prevProps.score !== this.props.score) {
document.title = `현재 점수: ${this.state.score}`;
}
// 로직 B의 업데이트 부분
if (prevProps.userId !== this.props.userId) {
NotificationAPI.unsubscribeFromUpdates(prevProps.userId, this.handleNotification);
NotificationAPI.subscribeToUpdates(this.props.userId, this.handleNotification);
}
}
componentWillUnmount() {
// 로직 B의 정리 부분
NotificationAPI.unsubscribeFromUpdates(this.props.userId, this.handleNotification);
}
}
- 함수형 컴포넌트: 상태 중심 & 관심사 분리 우수
- 코드는 "상태"와 "부수 효과"를 중심으로 구성됩니다. "이 컴포넌트는 어떤 상태에 따라 어떤 부수 효과를 생성해야 할까?"와 같은 고민이 필요합니다.
- 장점:
useEffect를 사용하면 관련 부수 효과의 설정 및 정리 코드를 함께 배치하여 더 나은 관심사 분리를 달성할 수 있습니다.
function UserProfile() {
const [score, setScore] = useState(0);
// 로직 A: document.title과 관련된 부수 효과
useEffect(() => {
document.title = `현재 점수: ${score}`;
}, [score]); // score에 의존
const [userId, setUserId] = useState(null);
// 로직 B: 알림 구독과 관련된 부수 효과
useEffect(() => {
if (userId) {
NotificationAPI.subscribeToUpdates(userId, handleNotification);
// 정리 함수와 설정 함수가 함께 배치됨!
return () => {
NotificationAPI.unsubscribeFromUpdates(userId, handleNotification);
};
}
}, [userId]); // userId에 의존
}
2. 상태와 클로저
- 클래스 컴포넌트: 항상 최신 상태에 접근
this.state와this.props는 모두 변경 가능하며 항상 최신 값을 가리킵니다. 일부 비동기 작업에서 예기치 않은 결과를 초래할 수 있습니다.
handleButtonClick() {
setTimeout(() => {
// 3초 후에 출력되는 값은 클릭 시점의 값이 아닌 최신 score
console.log(this.state.score);
}, 3000);
}
- 함수형 컴포넌트: 렌더링 시점의 값 캡처
- 각 렌더링은 독립적인 "스냅샷"이며 자신만의 props, state 및 이벤트 핸들러를 가집니다. 이것이 클로저의 본질입니다.
useState의 setter 함수는 안정적이지만, state와 props는 정의된 렌더링 시점에서 캡처됩니다.
function handleButtonClick() {
const currentScore = score; // 현재 렌더링의 score를 캡처
setTimeout(() => {
// 3초 후에 출력되는 값은 여전히 클릭 시점의 score
console.log(currentScore);
}, 3000);
}
- 최신 값을 읽어야 할 경우
useRef를 사용할 수 있습니다.
3. 로직 재사용
- 클래스 컴포넌트: 주로 HOC(고차 컴포넌트) 및 Render Props로 구현합니다. "래핑 지옥"(Wrapper Hell)을 유발하기 쉬우며 코드 구조가 복잡해질 수 있습니다.
- 함수형 컴포넌트: 커스텀 Hook으로 구현합니다. 일반 함수처럼 상태와 부수 효과 로직을 자유롭게 조합할 수 있으며 추가적인 컴포넌트 계층이 없어 코드가 더 명확합니다.
3. 설계 철학과 미래
- 클래스 컴포넌트: React 초기 ES6 Class를 받아들인 산물로, 더 명령형이며 라이프사이클을 강조합니다.
- 함수형 컴포넌트 + Hooks: React의 미래로, 더 선언형입니다. 개발자들이 라이프사이클 실행 시점이 아닌 "데이터 흐름"과 "부수 효과"에 더 집중하도록 합니다. React의 함수형 프로그래밍 사상을 더 잘 반영합니다.
요약
| 차이점 | 클래스 컴포넌트 | 함수형 컴포넌트 |
|---|---|---|
| 문법 | Class | Function |
| 상태/라이프사이클 | this.state, 라이프사이클 메서드 |
Hooks (useState, useEffect) |
| 사고 모델 | 라이프사이클 중심 | 상태와 부수 효과 중심 |
| 코드 구성 | 관심사 분리 불량 | 관심사 분리 우수 |
| 상태 캡처 | 항상 최신 값 | 렌더링 시점의 값 캡처 (클로저) |
| 로직 재사용 | HOC, Render Props | 커스텀 Hook |
| 학습 곡선 | this와 라이프사이클 이해 필요 |
클로저와 Hooks 규칙 이해 필요 |
| 미래 추세 | 레거시 | 주류와 미래 |
결론: 함수형 컴포넌트 + Hooks는 단순한 문법 단순화를 넘어 사고 모델의 업그레이드입니다. 클래스 컴포넌트가 로직 재사용, 코드 구성 및 이해에 있어 여러 가지 문제점을 해결하며 React의 미래 발전 방향을 대표합니다. 새 프로젝트에는 함수형 컴포넌트 사용을 강력히 권장합니다.