⭐ 2편 — 리액트 컴포넌트 구조 완전 이해하기: 초보자는 몰라서 헤매는 핵심 개념 정리
리액트를 처음 공부하면 “컴포넌트를 나눈다”는 말은 듣지만
정작 어디까지 나누고 어떻게 재사용해야 하는지 감이 잘 오지 않습니다.
저도 초기에 같은 고민을 반복했고, 잘못 설계한 컴포넌트 때문에
프로젝트 후반에 수정이 어려워 애를 먹었죠.
그래서 이번 글에서는 컴포넌트 구조를 제대로 이해하고 실무에서 활용하는 방법을 하나씩 설명해드릴게요.
✅ 컴포넌트(Component)란 무엇인가?
컴포넌트는 화면을 구성하는 최소 단위입니다.
버튼 하나
네비게이션 바
리스트 아이템
배너 컴포넌트 등
이 모든 게 컴포넌트가 될 수 있고, 작은 컴포넌트들이 모여 페이지를 이룹니다.
즉, 화면을 블록처럼 조립하는 방식이라고 보면 이해가 빠릅니다.
✅ 컴포넌트를 나누는 기준 — 여기서 90%가 갈린다
리액트에서 컴포넌트를 나누는 기준은 정답이 없지만,
실무에서 가장 효과적인 기준은 다음 네 가지입니다.
1) 재사용할 수 있는가?
버튼, 입력창, 카드 UI처럼 반복되는 UI는 컴포넌트로 분리하면 좋습니다.
예시:
function PrimaryButton({ children, onClick }) {
return <button onClick={onClick}>{children}</button>;
}
이렇게 만들어 두면 여러 페이지에서 공통으로 사용할 수 있습니다.
2) 한 가지 역할만 하는가? (Single Responsibility Principle)
컴포넌트는 하나의 기능, 하나의 역할만 담당하는 것이 좋습니다.
예를 들어:
❌ 잘못된 예
로그인 화면 전체를 하나의 컴포넌트로 합치기
리스트를 보여주고 정렬까지 처리하며 API 호출까지 함
✔ 좋은 예
<LoginForm />
<InputField />
<SubmitButton />
<LoginContainer /> (API 호출 담당)
하나의 컴포넌트가 너무 많은 일을 하면 수정이 어려워지고
리렌더링도 복잡해져 성능 문제로 이어집니다.
3) 상태(state)를 어디에서 관리해야 하는가?
컴포넌트 구조를 설계하는 데 아주 중요한 기준입니다.
✔ 기본 원칙
상태는 “그 값을 필요로 하는 가장 가까운 부모 컴포넌트”에서 관리해야 합니다.
예시:
리스트와 리스트 아이템이 있다면,
리스트 자체에서 상태를 관리하는 게 일반적입니다.
상태를 너무 아래로 내려버리면(child)
위에서 데이터를 다시 받아오기 어렵고 구조가 꼬입니다.
4) 데이터를 어디까지 내려보낼 것인가? (props drilling 고려)
props로 여러 단계 아래까지 데이터를 넘기다 보면
코드를 읽기 어려워지고 유지보수도 힘들어집니다.
이걸 props drilling 문제라고 합니다.
이 문제는 후에 배우게 될
✔ Context API
✔ Redux
같은 상태관리 도구를 사용하면 해결됩니다.
✅ 함수형 컴포넌트가 표준 — 왜 클래스형은 거의 안 쓰는가?
리액트에는 클래스형 컴포넌트, 함수형 컴포넌트
둘 다 있지만, 최근에는 함수형 컴포넌트 + Hooks 조합이 사실상 표준입니다.
이유는 간단합니다.
✔ 코드가 더 짧고
✔ 이해하기 쉽고
✔ 재사용이 편하고
✔ 성능도 더 안정적입니다.
실무에서는 거의 95% 이상이 함수형 컴포넌트라고 봐도 됩니다.
✅ 컴포넌트 파일 구조 실무 예시
리액트 초보자들이 가장 궁금해하는 것 중 하나가 “파일을 어떻게 배치해야 하느냐”—입니다.
실무에서 자주 쓰는 구조를 소개할게요.
src/
├─ components/
│ ├─ Button/
│ │ ├─ index.jsx
│ │ └─ style.css
│ ├─ Card/
│ │ └─ index.jsx
│ └─ NavBar/
│ └─ index.jsx
├─ pages/
│ ├─ Home.jsx
│ └─ Login.jsx
└─ App.jsx
이 구조의 장점은 명확합니다.
컴포넌트 단위가 눈에 잘 보임
재사용성과 유지보수가 쉬움
팀원이 봐도 빠르게 이해 가능
✅ 실전 예제: 컴포넌트 분리 제대로 해보기
아래는 초보자들이 자주 저지르는 “나쁜 구조”의 예입니다.
❌ 나쁜 예 (한 파일에 다 몰아넣기)
function App() {
const [count, setCount] = useState(0);
return (
<>
<h1>카운터</h1>
<button onClick={() => setCount(count + 1)}>증가</button>
<button onClick={() => setCount(count - 1)}>감소</button>
<p>현재 값: {count}</p>
</>
);
}
✔ 좋은 예 (역할 기반 분리)
function CounterDisplay({ count }) {
return <p>현재 값: {count}</p>;
}
function CounterButtons({ onIncrease, onDecrease }) {
return (
<>
<button onClick={onIncrease}>증가</button>
<button onClick={onDecrease}>감소</button>
</>
);
}
function CounterContainer() {
const [count, setCount] = useState(0);
return (
<>
<h1>카운터</h1>
<CounterButtons
onIncrease={() => setCount(count + 1)}
onDecrease={() => setCount(count - 1)}
/>
<CounterDisplay count={count} />
</>
);
}
이렇게 나누면
버튼 UI 변경 시 버튼 파일만 수정하면 되고
표시 방식 수정 시 디스플레이 파일만 변경하면 됩니다.
확장성, 유지보수성 모두 좋아져요.
✨ 마무리: 컴포넌트를 잘 나누면 프로젝트가 편해진다
컴포넌트를 나누는 기준을 잘 잡으면
프로젝트 후반에 구조가 무너지지 않고,
수정도 빠르게 할 수 있고,
리렌더링까지 자연스럽게 줄어듭니다.
이번 글에서는 개념 중심으로 다뤘지만,
다음 글에서는 props와 state를 깊이 있게 활용하는 방법을 실전 코드와 함께 정리해드릴게요.
이 부분은 리액트의 핵심이기 때문에 반드시 짚고 넘어가야 합니다.
'리액트' 카테고리의 다른 글
| ⭐ 9편 — 리액트 상태 관리 기본기: Context API vs Redux 실전 비교, 초보자도 알 수 있게 정리 (0) | 2025.11.25 |
|---|---|
| ⭐ 7편 — React Router 완전 정복: 페이지 이동부터 동적·중첩 라우팅까지 실전 가이드 (0) | 2025.11.25 |
| ⭐ 8편 — 리액트 리스트 렌더링 완전 정복: key가 중요한 이유와 성능 문제까지 한 번에 이해하기 (0) | 2025.11.25 |
| ⭐ 6편 — 리액트 리렌더링과 성능 최적화 완전 정복: React.memo, useCallback, useMemo 제대로 쓰는 법 (0) | 2025.11.25 |
| ⭐ 5편 — useRef 완전 정복: DOM 제어부터 값 저장까지 실무에서 자주 쓰는 패턴 총정리 (0) | 2025.11.25 |
| ⭐ 4편 — 리액트 핵심 Hook 완전 정복: useState와 useEffect 제대로 쓰는 법 (0) | 2025.11.25 |
| ⭐ 3편 — 초보자 80%가 헷갈리는 Props vs State: 완벽하게 이해하는 쉬운 설명 (0) | 2025.11.25 |
| ⭐ 1편 — 리액트(React)를 처음 시작할 때 가장 먼저 알아야 할 핵심 개념들 (0) | 2025.11.25 |