클래스는 객체를 찍어내기 위한 설계도(틀)다. 클래스 내부에는 상태를 저장하는 변수(프로퍼티)와 동작을 정의하는 함수(메서드)가 포함된다.
new 생성자() 문법을 통해 클래스를 바탕으로 실제 메모리에 생성된 실체를 인스턴스(Instance) 또는 객체(Object)라 부른다.function 키워드 없이 선언된 함수를 메서드(Method)라 부른다.
React 컴포넌트의 본질은 "props를 받아 UI(JSX)를 반환하는 것"으로 동일하지만, 문법과 내부 상태 관리 방식에서 차이가 있다.
// 1. 함수형 컴포넌트
export default function App() {
const name = '리액트';
return <div>{name}</div>;
}
// 2. 클래스형 컴포넌트
import { Component } from 'react';
export default class App extends Component {
render() {
const name = '리액트';
return <div>{name}</div>;
}
}
Component 클래스를 상속(extends Component)받아 작성한다.render() 메서드를 구현해야 하며, 그 안에서 JSX를 반환한다.this.state와 this.setState를 통해 컴포넌트 상태를 관리한다.state)나 생명주기 API를 쓸 수 없었지만, React 16.8에서 Hook(useState, useEffect 등)이 도입되면서 클래스형 컴포넌트의 모든 기능을 완전히 대체하게 되었다.클래스형 컴포넌트를 작성할 때 가장 흔하게 발생하는 런타임 에러는 메서드 내에서 this.setState is not a function 또는 Cannot read properties of undefined (reading 'setState')가 뜨는 현상이다.
일반 함수(function 키워드 선언문) 내부의 this는 함수가 어디서 정의되었는지가 아니라, "누가 이 함수를 호출했는가(호출 시점의 실행 컨텍스트)"에 따라 동적으로 결정된다.
// ❌ 에러가 발생하는 패턴
export default class CounterPage extends Component {
state = { count: 0 };
onClickCounter() {
// 버튼 클릭 시 이벤트 리스너에 의해 호출되면 this는 컴포넌트 인스턴스를 잃어버리고 undefined가 된다.
this.setState((prev) => ({ count: prev.count + 1 }));
}
render() {
return (
<button onClick={this.onClickCounter}>카운트 증가</button>
);
}
}
onClick={this.onClickCounter}로 함수를 전달하면, 나중에 버튼을 클릭할 때 React 이벤트 시스템이 해당 함수를 독립적으로 실행하므로 내부의 this가 컴포넌트 인스턴스를 가리키지 못한다.
화살표 함수는 자체적인 this 바인딩을 갖지 않고, 함수가 선언된 시점의 상위 스코프의 this를 그대로 가져다 쓴다(Lexical this).
클래스 필드 문법을 사용해 메서드를 화살표 함수로 정의하면 인스턴스 생성 시점에 this가 컴포넌트 자신으로 영구 고정된다.
import { Component } from 'react';
export default class CounterPage extends Component {
state = {
count: 99,
};
// 화살표 함수로 선언하여 Lexical this 유지
onClickCounter = () => {
this.setState((prev) => ({
count: prev.count + 1,
}));
};
render() {
return (
<div>
<div>현재 카운트: {this.state.count}</div>
<button onClick={this.onClickCounter}>카운트 올리기</button>
</div>
);
}
}
bind(this)로 직접 명시적 바인딩일반 함수 문법을 유지해야 한다면, bind() 메서드를 사용해 함수의 this를 컴포넌트 인스턴스로 강제 묶어주어야 한다.
import { Component } from 'react';
export default class CounterPage extends Component {
state = {
count: 99,
};
// 일반 함수로 정의
onClickCounter() {
this.setState((prev) => ({
count: prev.count + 1,
}));
}
render() {
return (
<div>
<div>현재 카운트: {this.state.count}</div>
{/* bind(this)를 통해 렌더 시점의 컴포넌트 인스턴스를 this로 고정 */}
<button onClick={this.onClickCounter.bind(this)}>카운트 올리기</button>
</div>
);
}
}
(단, JSX 내부에서 매번 .bind(this)를 호출하면 렌더링될 때마다 새로운 함수 객체가 생성되므로, 보통 생성자(constructor) 내부에서 미리 바인딩해 두는 방식을 썼다.)
extends Component로 생명주기와 state 기능을 상속받아 사용하며, 반드시 render() 메서드로 JSX를 반환해야 한다.this가 동적으로 변하므로, 이벤트 핸들러로 넘겨줄 때 컴포넌트 인스턴스를 잃어버린다.bind(this) 보일러플레이트를 줄이는 가장 깔끔한 해결책이다.