[이제와서 시작하는 React 마스터하기 #4] Props - 컴포넌트끼리 대화하기
이 포스트를 읽고 나면 ✅ Props가 무엇인지 이해할 수 있어요 ✅ 부모와 자식 컴포넌트가 데이터를 주고받을 수 있어요 ✅ 재사용 가능한 컴포넌트를 만들 수 있어요
📚 시작하기 전에
이전 포스트 Day 3: useState에서 화면을 움직이게 만드는 방법을 배웠어요.
하지만 지금까지는 하나의 컴포넌트 안에서만 작업했죠?
실제 앱은 여러 컴포넌트로 나누어져 있어요:
1
2
3
4
5
6
7
8
9
10
앱
├── 헤더
├── 사이드바
├── 메인 컨텐츠
│ ├── 포스트 목록
│ │ ├── 포스트 1
│ │ ├── 포스트 2
│ │ └── 포스트 3
│ └── 페이지네이션
└── 푸터
“이 컴포넌트들이 어떻게 서로 대화할까요?”
바로 Props를 사용해요!
🤔 Props가 뭐예요?
Props는 Properties(속성)의 줄임말이에요.
쉽게 말하면:
- 부모 컴포넌트에서 자식 컴포넌트로 전달하는 데이터
- 함수의 매개변수(파라미터)와 비슷해요!
일상 생활의 Props
1
2
3
4
5
6
7
8
엄마 → 딸에게 심부름
"마트에 가서 우유(props) 사와!"
선생님 → 학생에게 숙제
"이 문제집(props) 10페이지까지 풀어와!"
앱 → 버튼 컴포넌트
"이 텍스트(props)를 보여줘!"
🎯 Props 첫 만남
가장 간단한 예제
먼저 컴포넌트를 분리해볼게요.
Before: 한 컴포넌트에 모든 코드
1
2
3
4
5
6
7
8
function App() {
return (
<div>
<h1>안녕하세요, 홍길동님!</h1>
<p>환영합니다!</p>
</div>
);
}
After: 컴포넌트 분리 + Props 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 자식 컴포넌트
function Greeting(props) {
return (
<div>
<h1>안녕하세요, {props.name}님!</h1>
<p>환영합니다!</p>
</div>
);
}
// 부모 컴포넌트
function App() {
return (
<div>
<Greeting name="홍길동" />
</div>
);
}
export default App;
실행해보기
src/App.jsx에 위 코드를 입력하고 저장하세요!
결과: “안녕하세요, 홍길동님!”이 보여요.
코드 이해하기
1. Props 전달하기 (부모 → 자식)
1
<Greeting name="홍길동" />
name="홍길동":name이라는 prop을 전달- HTML 속성처럼 써요!
2. Props 받기 (자식)
1
2
3
function Greeting(props) {
return <h1>안녕하세요, {props.name}님!</h1>;
}
props: 전달받은 모든 데이터가 담긴 객체props.name:nameprop에 접근
💡 Props의 특징
1. 읽기 전용 (Read-only)
1
2
3
4
5
6
7
function Greeting(props) {
// ❌ Props를 바꿀 수 없어요!
props.name = "김철수"; // 에러!
// ✅ Props는 읽기만 해요
return <h1>{props.name}</h1>;
}
왜 그럴까요?
Props는 부모가 주는 데이터예요. 자식이 마음대로 바꾸면 안 되죠!
2. 여러 개 전달 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function UserCard(props) {
return (
<div>
<h2>{props.name}</h2>
<p>나이: {props.age}살</p>
<p>직업: {props.job}</p>
</div>
);
}
function App() {
return (
<UserCard
name="홍길동"
age={25}
job="개발자"
/>
);
}
주의: 문자열이 아닌 값은 중괄호 {}로 감싸요!
- 문자열:
name="홍길동" - 숫자:
age={25} - 불린:
isActive={true} - 배열:
tags={['react', 'javascript']}
3. 구조 분해 할당으로 간단하게
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Before: props.name, props.age...
function UserCard(props) {
return (
<div>
<h2>{props.name}</h2>
<p>{props.age}살</p>
</div>
);
}
// After: 바로 name, age로 사용
function UserCard({ name, age, job }) {
return (
<div>
<h2>{name}</h2>
<p>{age}살</p>
<p>{job}</p>
</div>
);
}
이제부터는 구조 분해 방식을 쓸게요! (더 깔끔하거든요 😊)
💪 실습 1: 버튼 컴포넌트 만들기
재사용 가능한 버튼을 만들어봐요!
목표
1
2
3
4
같은 버튼 컴포넌트를 사용하지만
- 텍스트가 다르고
- 색상이 다르고
- 클릭하면 다른 동작을 해요
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import { useState } from 'react';
// 버튼 컴포넌트
function Button({ text, color, onClick }) {
return (
<button
onClick={onClick}
style={{
backgroundColor: color,
color: 'white',
padding: '10px 20px',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
margin: '5px'
}}
>
{text}
</button>
);
}
// 앱 컴포넌트
function App() {
const [message, setMessage] = useState('');
return (
<div style={{ padding: '20px' }}>
<h1>버튼 예제</h1>
<Button
text="성공"
color="#28a745"
onClick={() => setMessage('성공 버튼을 클릭했어요!')}
/>
<Button
text="경고"
color="#ffc107"
onClick={() => setMessage('경고 버튼을 클릭했어요!')}
/>
<Button
text="위험"
color="#dc3545"
onClick={() => setMessage('위험 버튼을 클릭했어요!')}
/>
{message && (
<p style={{ marginTop: '20px', fontSize: '18px' }}>
{message}
</p>
)}
</div>
);
}
export default App;
결과 확인
버튼 3개가 다른 색으로 보이고, 각각 클릭하면 다른 메시지가 나와요!
이게 Props의 힘이에요! 🎉 하나의 컴포넌트를 여러 곳에서 다르게 사용할 수 있어요.
💪 실습 2: 프로필 카드 만들기
해보기
여러 사람의 프로필 카드를 만들어봐요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import { useState } from 'react';
// 프로필 카드 컴포넌트
function ProfileCard({ name, job, age, email, image }) {
return (
<div
style={{
border: '1px solid #ddd',
borderRadius: '10px',
padding: '20px',
margin: '10px',
maxWidth: '300px'
}}
>
<img
src={image}
alt={name}
style={{
width: '100px',
height: '100px',
borderRadius: '50%',
objectFit: 'cover'
}}
/>
<h2>{name}</h2>
<p style={{ color: '#666' }}>{job}</p>
<p>나이: {age}살</p>
<p>이메일: {email}</p>
</div>
);
}
// 앱 컴포넌트
function App() {
return (
<div style={{ padding: '20px' }}>
<h1>우리 팀 소개</h1>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
<ProfileCard
name="홍길동"
job="프론트엔드 개발자"
age={25}
email="hong@example.com"
image="https://via.placeholder.com/100"
/>
<ProfileCard
name="김철수"
job="백엔드 개발자"
age={28}
email="kim@example.com"
image="https://via.placeholder.com/100"
/>
<ProfileCard
name="이영희"
job="디자이너"
age={26}
email="lee@example.com"
image="https://via.placeholder.com/100"
/>
</div>
</div>
);
}
export default App;
직접 해보기
위 코드에서 네 번째 팀원을 추가해보세요!
💪 실습 3: 자식 → 부모로 데이터 전달하기
지금까지는 부모 → 자식으로만 전달했어요.
반대로는 어떻게 할까요?
핵심 아이디어
함수를 Props로 전달하면 돼요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import { useState } from 'react';
// 자식 컴포넌트
function TodoInput({ onAdd }) {
const [input, setInput] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (input.trim()) {
onAdd(input); // 부모에게 데이터 전달!
setInput('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="할 일을 입력하세요"
style={{ padding: '5px', marginRight: '5px' }}
/>
<button type="submit">추가</button>
</form>
);
}
// 부모 컴포넌트
function App() {
const [todos, setTodos] = useState([]);
const handleAddTodo = (todoText) => {
console.log('자식으로부터 받은 데이터:', todoText);
setTodos([...todos, { id: Date.now(), text: todoText }]);
};
return (
<div style={{ padding: '20px' }}>
<h1>할 일 목록</h1>
{/* 함수를 Props로 전달 */}
<TodoInput onAdd={handleAddTodo} />
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
}
export default App;
흐름 이해하기
- 부모가
handleAddTodo함수를 만듦 - 그 함수를
onAddprop으로 자식에게 전달 - 자식이
onAdd(input)을 호출 - 부모의
handleAddTodo가 실행됨 - 부모의 state가 업데이트됨!
이게 React의 데이터 흐름이에요! 📊
💪 실습 4: children Props
특별한 prop이 하나 더 있어요: children
children이 뭐예요?
컴포넌트 태그 사이에 있는 내용이에요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 카드 컴포넌트
function Card({ title, children }) {
return (
<div
style={{
border: '2px solid #0066cc',
borderRadius: '10px',
padding: '20px',
margin: '10px'
}}
>
<h2 style={{ borderBottom: '1px solid #ddd', paddingBottom: '10px' }}>
{title}
</h2>
<div style={{ marginTop: '10px' }}>
{children}
</div>
</div>
);
}
// 사용 예제
function App() {
return (
<div style={{ padding: '20px' }}>
<Card title="공지사항">
<p>내일은 휴무일입니다.</p>
<p>즐거운 주말 보내세요!</p>
</Card>
<Card title="사용자 정보">
<p>이름: 홍길동</p>
<p>이메일: hong@example.com</p>
<button>수정</button>
</Card>
<Card title="이미지">
<img
src="https://via.placeholder.com/300x200"
alt="샘플"
style={{ width: '100%' }}
/>
</Card>
</div>
);
}
export default App;
children의 장점:
- 재사용 가능한 레이아웃 컴포넌트를 만들 수 있어요
- 모달, 카드, 박스 같은 걸 쉽게 만들어요
⚠️ 자주 하는 실수
실수 1: Props를 바꾸려고 하기
1
2
3
4
5
6
function Greeting({ name }) {
// ❌ Props는 읽기 전용!
name = "다른이름"; // 안 돼요!
return <h1>{name}</h1>;
}
해결책: 값을 바꾸고 싶다면 state를 써요!
1
2
3
4
5
6
7
8
9
10
11
12
function Greeting({ initialName }) {
const [name, setName] = useState(initialName);
return (
<div>
<h1>{name}</h1>
<button onClick={() => setName("새이름")}>
이름 바꾸기
</button>
</div>
);
}
실수 2: Props 이름 오타
1
2
3
4
5
6
7
// 부모
<UserCard userName="홍길동" />
// 자식 - 오타!
function UserCard({ username }) { // userName이 아니라 username
return <h1>{username}</h1>; // undefined가 나와요!
}
해결책: 정확히 같은 이름을 써요!
실수 3: 중괄호 빠뜨리기
1
2
3
4
5
// ❌ 숫자인데 따옴표로 쓰면
<UserCard age="25" /> // 문자열 "25"가 전달돼요
// ✅ 중괄호를 써요
<UserCard age={25} /> // 숫자 25가 전달돼요
🎯 종합 실습: 댓글 시스템 만들기
배운 내용을 모두 활용해서 댓글 시스템을 만들어봐요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { useState } from 'react';
// 댓글 컴포넌트
function Comment({ author, text, time, onDelete }) {
return (
<div
style={{
border: '1px solid #ddd',
borderRadius: '5px',
padding: '10px',
marginBottom: '10px'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<strong>{author}</strong>
<button onClick={onDelete} style={{ color: 'red', cursor: 'pointer' }}>
삭제
</button>
</div>
<p style={{ margin: '10px 0' }}>{text}</p>
<small style={{ color: '#999' }}>{time}</small>
</div>
);
}
// 댓글 입력 컴포넌트
function CommentInput({ onSubmit }) {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
onSubmit(text);
setText('');
}
};
return (
<form onSubmit={handleSubmit} style={{ marginBottom: '20px' }}>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="댓글을 입력하세요..."
style={{
width: '100%',
padding: '10px',
borderRadius: '5px',
border: '1px solid #ddd',
marginBottom: '10px'
}}
rows="3"
/>
<button type="submit" style={{ padding: '5px 15px' }}>
댓글 달기
</button>
</form>
);
}
// 메인 앱
function App() {
const [comments, setComments] = useState([
{
id: 1,
author: '홍길동',
text: '정말 유익한 글이네요!',
time: '5분 전'
},
{
id: 2,
author: '김철수',
text: '저도 도움이 되었습니다.',
time: '10분 전'
}
]);
const handleAddComment = (text) => {
const newComment = {
id: Date.now(),
author: '나',
text: text,
time: '방금 전'
};
setComments([newComment, ...comments]);
};
const handleDeleteComment = (id) => {
setComments(comments.filter(comment => comment.id !== id));
};
return (
<div style={{ padding: '20px', maxWidth: '600px', margin: '0 auto' }}>
<h1>댓글 ({comments.length})</h1>
<CommentInput onSubmit={handleAddComment} />
<div>
{comments.map(comment => (
<Comment
key={comment.id}
author={comment.author}
text={comment.text}
time={comment.time}
onDelete={() => handleDeleteComment(comment.id)}
/>
))}
</div>
{comments.length === 0 && (
<p style={{ color: '#999', textAlign: 'center' }}>
아직 댓글이 없습니다. 첫 댓글을 달아보세요!
</p>
)}
</div>
);
}
export default App;
📝 정리
자, Day 4를 마무리할 시간이에요!
오늘 배운 내용 체크리스트
- Props가 무엇인지 이해했어요
- 부모에서 자식으로 데이터를 전달할 수 있어요
- 자식에서 부모로 함수를 통해 데이터를 전달할 수 있어요
- children props를 사용할 수 있어요
- 재사용 가능한 컴포넌트를 만들 수 있어요
- 댓글 시스템을 만들었어요
핵심 요약 3줄
- Props: 부모가 자식에게 전달하는 데이터 (읽기 전용!)
- 전달 방법:
<Child name="값" age={25} /> - 역방향 통신: 함수를 Props로 전달해서 자식이 호출
💪 숙제 (선택사항)
- 좋아요 버튼 컴포넌트
- props로 초기 좋아요 수를 받아요
- 버튼을 누르면 좋아요 수가 증가해요
- 한 번 더 누르면 취소돼요
- 별점 컴포넌트
- props로 별점(1-5)을 받아요
- 별점만큼 ⭐을 표시해요
- 클릭하면 별점을 바꿀 수 있어요
- 모달 컴포넌트
- children을 사용해서 내용을 받아요
- 닫기 버튼을 클릭하면 모달이 사라져요
🚀 다음 단계
축하합니다! Props를 마스터했어요! 🎉
이제 컴포넌트끼리 대화할 수 있어요. 하지만 아직 한 가지 더 배울 게 있어요.
“컴포넌트가 화면에 나타난 후에 뭔가를 하고 싶다면?” 예를 들어 데이터를 불러온다든지, 타이머를 시작한다든지…
다음 포스트에서는 useEffect를 배워서 컴포넌트의 생명주기를 다루는 방법을 알아볼 거예요!
➡️ Day 5: useEffect - 컴포넌트 생명주기에서 만나요!
💬 Props가 헷갈리나요?
처음엔 모두 그래요! “부모 → 자식은 알겠는데, 자식 → 부모는 왜 이렇게 복잡해?”
핵심을 기억하세요: React는 항상 위에서 아래로 데이터가 흘러요!
자식이 부모에게 뭔가를 알려주려면? → 부모가 준 함수를 호출하는 거예요.
몇 번 써보면 자연스러워져요. 댓글 시스템 예제를 직접 타이핑하면서 흐름을 이해해보세요! 💪
🎓 시리즈 목록
Phase 1: React 기초 (Day 1-8)
- React, 왜 배워야 할까요?
- 첫 React 앱 만들어보기
- useState - 화면을 움직이게 만들기
- Props - 컴포넌트끼리 대화하기 (현재 포스트)
- 조건부 렌더링과 리스트 렌더링
- 컴포넌트 생명주기 이해하기
- Context API로 전역 상태 관리하기
- Custom Hooks 만들기
Phase 2: React 활용 (Day 9-14)
Phase 3: React 심화 (Day 15-20)
🔗 유용한 리소스
- React 공식 문서 - Props - Props 완벽 가이드
- React 공식 문서 - 컴포넌트 - 컴포넌트 기초
- JavaScript 구조 분해 - 구조 분해 할당
