1. React 컴포넌트란?
React 컴포넌트는 재사용 가능한 UI 구성요소임. 레고 블록처럼 여러 컴포넌트를 조합해서 복잡한 웹 애플리케이션을 만들 수 있음

컴포넌트의 핵심 특징
- 입력(Props)을 받아서 UI(React 엘리먼트)를 반환함
- 독립적이고 재사용 가능함
- 자체적인 로직과 스타일을 포함할 수 있음
- 다른 컴포넌트와 조합하여 복잡한 UI를 구성함
왜 컴포넌트를 사용할까?? 전통적인 웹 개발에서는 HTML, CSS, JavaScript를 각각 분리했음.
React는 "기술별 분리" 대신 "기능별 분리"를 함
장점:
- 코드 재사용성이 높아짐
- 유지보수가 쉬워짐
- 테스트가 용이함
- 협업이 효율적임
- 관련된 코드가 한 곳에 모여 있어서 이해하기 쉬움
2. 컴포넌트의 핵심 개념
React는 선언형 프로그래밍 방식으로 UI를 작성함.
"어떻게" 보다는 "무엇을" 그릴지에 집중함
명령형 방식
const button = document.createElement('button');
button.textContent = '클릭하세요';
button.addEventListener('click', handleClick);
document.body.appendChild(button);
선언형 방식
function MyButton() {
return <button onClick={handleClick}>클릭하세요</button>;
}
단방향 데이터 흐름
React에서 데이터는 부모에서 자식으로 한 방향으로만 흐름. 이를통해 데이터의흐름을 예측 가능하게 만들고 디버깅을 쉽게함
부모 컴포넌트 (데이터 소유)
↓ props
자식 컴포넌트 (데이터 사용)
↓ props
손자 컴포넌트 (데이터 사용)
컴포넌트 합성
작은 컴포넌트들을 조합하여 큰 컴포넌트를 만드는 방식임
function Avatar({ src, alt }) {
return <img src={src} alt={alt} className="avatar" />;
}
function UserInfo({ user }) {
return (
<div className="user-info">
<Avatar src={user.avatarUrl} alt={user.name} />
<span>{user.name}</span>
</div>
);
}
function Comment({ author, text, date }) {
return (
<div className="comment">
<UserInfo user={author} />
<p>{text}</p>
<span>{date}</span>
</div>
);
}
3. 컴포넌트의 종류
3-1. 함수형 컴포넌트
현재 React에서 권장하는 방식임.
Hooks의 도입으로 함수형 컴포넌트에서도 상태 관리와 생명주기 기능을 모두 사용할 수 있게 됨.
기본 형태
function Welcome(props) {
return <h1>안녕하세요, {props.name}님!</h1>;
}
// 화살표 함수 사용
const Welcome = (props) => {
return <h1>안녕하세요, {props.name}님!</h1>;
};
// 구조 분해 할당 사용
const Welcome = ({ name }) => {
return <h1>안녕하세요, {name}님!</h1>;
};
// 암시적 반환 (한 줄)
const Welcome = ({ name }) => <h1>안녕하세요, {name}님!</h1>;
함수형 컴포넌트의 장점:
- 간결한 문법으로 코드가 짧고 읽기 쉬움
- 클래스보다 성능이 약간 더 좋음
- Hooks 사용 가능 (useState, useEffect 등)
- 테스트가 쉬움 (순수 함수에 가까움)
- this 키워드를 사용하지 않아 JavaScript의 복잡한 this 바인딩 문제가 없음
3-2. 클래스형 컴포넌트 (Class Component)
React 16.8 이전의 주요 방식이었으며, 현재는 레거시 코드에서 주로 볼 수 있음.
import React, { Component } from 'react';
class Welcome extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
console.log('컴포넌트가 마운트되었습니다');
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<h1>안녕하세요, {this.props.name}님!</h1>
<p>카운트: {this.state.count}</p>
<button onClick={this.handleClick}>증가</button>
</div>
);
}
}
클래스형 vs 함수형 비교
| 특징 | 함수형 컴포넌트 | 클래스형 컴포넌트 |
| 문법 | 간결함 | 복잡함 |
| 상태 관리 | useState Hook | this.state |
| 생명주기 | useEffect Hook | 생명주기 메서드 |
| this 바인딩 | 불필요 | 필요 |
| 성능 | 약간 더 좋음 | 약간 느림 |
| 코드 재사용 | Custom Hooks | HOC, Render Props |
| 권장 여부 | 권장 | 레거시 |
3-3. 제어 컴포넌트 vs 비제어 컴포넌트
제어 컴포넌트
React State가 "신뢰 가능한 단일 출처"가 되는 방식임
function ControlledInput() {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
};
return (
<input
type="text"
value={value}
onChange={handleChange}
/>
);
}
장점
- 값의 유효성 검사가 쉬움
- 실시간 포맷팅 가능
- 조건부 렌더링이 쉬움
비제어 컴포넌트
DOM 자체가 데이터를 관리하는 방식임
ref를 사용하여 필요할 때만 값을 가져옴
function UncontrolledInput() {
const inputRef = useRef(null);
const handleSubmit = () => {
alert(`입력값: ${inputRef.current.value}`);
};
return (
<>
<input type="text" ref={inputRef} />
<button onClick={handleSubmit}>제출</button>
</>
);
}
장점
- 코드가 간단함
- 파일 입력 등 DOM API가 필요한 경우 유용함
4. Props - 컴포넌트 간 데이터 전달
Props는 `properties`의 줄임말로, 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 메커니즘임
Props의 특징:
- 읽기 전용 (Read-only): 자식 컴포넌트에서 props를 수정할 수 없음
- 단방향 데이터 흐름: 부모 → 자식 방향으로만 전달됨
- 모든 타입 전달 가능: 문자열, 숫자, 객체, 배열, 함수 등 모든 JavaScript 값을 전달할 수 있음
기본 사용법
// 부모 컴포넌트
function App() {
return (
<div>
<Greeting name="철수" age={25} />
<Greeting name="영희" age={23} />
</div>
);
}
// 자식 컴포넌트
function Greeting(props) {
return (
<div>
<h1>안녕하세요, {props.name}님!</h1>
<p>나이: {props.age}세</p>
</div>
);
}
구조 분해 할당
// props 객체를 바로 분해
function Greeting({ name, age }) {
return (
<div>
<h1>안녕하세요, {name}님!</h1>
<p>나이: {age}세</p>
</div>
);
}
// 기본값 설정
function Greeting({ name = "게스트", age = 0 }) {
return (
<div>
<h1>안녕하세요, {name}님!</h1>
<p>나이: {age}세</p>
</div>
);
}
다양한 타입의 Props 전달
function Profile() {
const user = {
name: "김철수",
avatar: "/images/avatar.jpg"
};
const handleClick = () => {
alert("클릭!");
};
return (
<UserCard
// 문자열
title="사용자 프로필"
// 숫자
count={42}
// 불리언
isActive={true}
// 객체
user={user}
// 배열
tags={["React", "JavaScript", "Web"]}
// 함수
onClick={handleClick}
// JSX
header={<h1>헤더</h1>}
/>
);
}
Children Prop
children은 특별한 prop으로, 컴포넌트 태그 사이의 내용을 전달함
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
// 사용
function App() {
return (
<Card>
<h2>제목</h2>
<p>이것은 카드 내용입니다.</p>
</Card>
);
}
Props 전달 패턴
// Spread 연산자로 여러 props 한 번에 전달
function App() {
const userProps = {
name: "철수",
age: 25,
email: "chulsu@example.com"
};
return <UserProfile {...userProps} />;
}
// Props Drilling (여러 단계 전달)
function GrandParent() {
const data = "중요한 데이터";
return <Parent data={data} />;
}
function Parent({ data }) {
return <Child data={data} />;
}
function Child({ data }) {
return <div>{data}</div>;
}
Props 검증 (PropTypes)
개발 중에 props의 타입을 검증하여 버그를 방지할 수 있음.
import PropTypes from 'prop-types';
function Greeting({ name, age, email }) {
return (
<div>
<h1>{name}</h1>
<p>{age}세</p>
<p>{email}</p>
</div>
);
}
Greeting.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
email: PropTypes.string
};
Greeting.defaultProps = {
email: "이메일 없음"
};
내용이 많아서 다음 글에서 이어서 정리해보겠음