React 컴포넌트는 props를 받아서 화면에 그려질 UI(JSX)를 리턴하는, 재사용 가능한 조각 입니다. 버튼 하나, 리스트 아이템 하나처럼 작은 단위일 수도 있고, 여러 컴포넌트를 조합한 페이지 전체일 수도 있어요.
import { useState } from "react";
import ReviewList from "./components/ReviewList";
import Modal from "./components/Modal";
import ReviewForm from "./components/ReviewForm";
import Button from "./components/Button";
import Layout from "./components/Layout";
import mockItems from "./mock.json";
import styles from "./App.module.css";
import useTranslate from "./hooks/useTranslate";
function App() {
const t = useTranslate();
const [items, setItems] = useState(mockItems);
const [order, setOrder] = useState("createdAt");
const [isCreateReviewOpen, setIsCreateReviewOpen] = useState(false);
const sortedItems = [...items].sort((a, b) => b[order] - a[order]);
const handleCreate = (data) => {
const now = new Date();
const newItem = {
id: items.length + 1,
...data,
createdAt: now.valueOf(),
updatedAt: now.valueOf(),
};
setItems([newItem, ...items]);
setIsCreateReviewOpen(false);
};
const handleUpdate = (id, data) => {
const index = items.findIndex((item) => item.id === id);
const now = new Date();
const newItem = {
...items[index],
...data,
updatedAt: now.valueOf(),
};
const newItems = [...items.slice(0, index), newItem, ...items.slice(index + 1)];
setItems(newItems);
};
const handleDelete = (id) => {
const nextItems = items.filter((item) => item.id !== id);
setItems(nextItems);
};
return (
<Layout>
<div className={styles.buttons}>
<div>
<Button
className={styles.orderButton}
variant={order === "createdAt" ? "primary" : "ghost"}
onClick={() => setOrder("createdAt")}
>
{t("sort by latest")}
</Button>
<Button
className={styles.orderButton}
variant={order === "rating" ? "primary" : "ghost"}
onClick={() => setOrder("rating")}
>
{t("sort by best")}
</Button>
</div>
<Button className={styles.createButton} onClick={() => setIsCreateReviewOpen(true)}>
추가하기
</Button>
<Modal isOpen={isCreateReviewOpen} onClose={() => setIsCreateReviewOpen(false)}>
<h2 className={styles.modalTitle}>리뷰 생성</h2>
<ReviewForm onSubmit={handleCreate} />
</Modal>
</div>
<ReviewList items={sortedItems} onUpdate={handleUpdate} onDelete={handleDelete} />
</Layout>
);
}
export default App;
리액트에서 컴포넌트라는 개념을 사용하는 핵심 이유는 세가지로 볼 수 있습니다.
<Layout />처럼 태그 형태로 계속 가져다 쓸 수 있음
리액트 컴포넌트는 두개의 부분으로 나눌 수 있어요. 하나는 정의하는 부분이며, 다른 하나는 사용하는 부분입니다. 정의하는 부분은 function으로 컴포넌트를 만들면서 어떤 props가 필요한지 매개변수 자리를 설계하는 곳이고, 사용하는 부분은 그 컴포넌트를 태그처럼 갖다 쓰면서 실제 값을 채워 넣는 곳이에요.

function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
그냥 함수예요. useState, useEffect 같은 훅으로 상태와 생명주기를 관리해요. 훅은 React 16.8(2019년 2월)부터 쓸 수 있게 됐어요.
같은 기능을 클래스형으로 짜면 이렇게 만들어 볼 수 있습니다.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this); // this 바인딩 필요
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>;
}
}
React.Component를 상속받는 클래스고, 반드시 render()를 구현해야 해요. 상태는 this.state, 업데이트는 this.setState()로 관리해요.

| 함수형 컴포넌트 | 클래스형 컴포넌트 | |
|---|---|---|
| 형태 | 함수 | 클래스 (React.Component 상속) |
| 상태 관리 | useState | this.state, this.setState() |
| 생명주기 | useEffect 하나로 대부분 처리 | componentDidMount 등 여러 메서드로 분산 |
this | 신경 안 써도 됨 | 메서드마다 바인딩 필요, 실수하기 쉬움 |
| 로직 재사용 | 커스텀 훅으로 간단하게 | HOC, render props 같은 복잡한 패턴 필요 |
| 현재 위치 | 사실상 표준 | 레거시 코드에서 주로 발견됨 |
사실 리액트 초창기엔 상태(state)를 다루려면 무조건 클래스형으로 만들어야 했어요. 그러다 2019년, React가 훅(Hook)을 새로 도입하면서 상황이 바뀌었습니다.
훅이 생기기 전엔, 상태 하나 다루려고 해도 constructor, this.state, render() 같은 클래스 문법을 통째로 알아야 했어요. 근데 훅 덕분에, 위에서 본 것처럼 함수 안에 useState 한 줄만 써도 똑같은 걸 훨씬 짧고 간단하게 만들 수 있게 됐어요.
그래서 요즘 새로 만드는 리액트 프로젝트는 대부분 함수형으로 짜여요. 클래스 문법을 몰라도 리액트를 시작할 수 있게 된 거고, 이에 따라 우리도 처음부터 자연스럽게 함수형과 훅의 형태로 배우고 있는 거예요.
클래스형 컴포넌트의 가장 큰 문제는 this 바인딩 문제예요. 클래스형에서는 이벤트 핸들러 안 this가 엉뚱한 걸 가리키는 실수가 흔해서, 매번 .bind(this)를 해주거나 화살표 함수로 우회해야 했어요. 또, 로직 재사용이 훨씬 쉬워요. 함수형과 훅에서는 반복되는 상태 관리 로직을 그냥 커스텀 훅 하나로 뽑아내서 여러 컴포넌트가 나눠 쓸 수 있거든요.