리액트의 심장은 컴포넌트예요. 화면을 작은 부품으로 쪼개 재사용하고, 그 부품끼리는 props라는 통로로 데이터를 주고받아요. 이 두 개념만 잡으면 리액트가 갑자기 쉬워집니다.
컴포넌트(Component)는 독립적이고 재사용 가능한 UI 조각(부품)이에요. 버튼 하나, 카드 하나, 헤더 하나가 각각 컴포넌트가 될 수 있어요. 이 부품들을 레고처럼 조립해서 페이지 전체를 만듭니다.
우리가 배우는 방식은 함수 컴포넌트예요. 말 그대로 "대문자로 시작하는 함수가 JSX를 return"하면 그게 컴포넌트예요. 정말 이게 전부입니다.
// 함수 컴포넌트 정의 — 대문자 Welcome, JSX를 return
function Welcome() {
return 안녕
;
}
// 사용 — HTML 태그처럼 로 씀
<Welcome />처럼 태그로 사용할 수 있어요.
"화면의 한 조각을 만드는 함수" — 그게 컴포넌트예요. 한 번 만들면 여러 곳에서 몇 번이든 재사용합니다.
function Welcome() {
return <h1>안녕하세요!</h1>;
}
function Welcome() → JavaScript 함수return → JavaScript 문법<h1>안녕하세요!</h1> → JSX (React의 문법)Welcome을 나중에 <Welcome />처럼 쓰는 것 → React 컴포넌트로서 사용하는 것
function add(a, b) {
return a + b; // 숫자를 계산해서 값을 리턴
}
function Button() {
return <button>클릭</button>; // 숫자가 아니라 화면(UI)을 리턴
}
Header, Button, Footer)App)
function App() {
return (
<div>
<Header />
<Button />
<Footer />
</div>
);
}
| JavaScript가 제공하는 것 | React가 제공하는 것 |
|---|---|
| 변수 | 컴포넌트 |
| 함수 | JSX |
| 객체 | props |
| 배열 | state |
| 클래스 | Hooks (useState, useEffect 등) |
| 모듈(import/export) | |
function add(a, b) {
return a + b;
}
export default add; // 이 함수를 다른 파일에서 쓸 수 있게 내보냄import add from './math.js'; // math.js에서 add 함수를 가져옴
console.log(add(2, 3)); // 5
import React from 'react';
import Header from './Header'; // 내가 만든 Header 컴포넌트 가져오기
function App() {
return <Header />;
}
export default App; // App 컴포넌트를 다른 곳에서 쓸 수 있게 내보내기import/export입니다.Welcome, Button ○ / welcome, button ✗)
function Welcome() {
return <h1>안녕하세요</h1>;
}
<h1>이 그려집니다.
function Secret({ isLogin }) {
if (!isLogin) {
return null;
}
return <h1>비밀 페이지</h1>;
}
null 반환 → 화면에 아무것도 안 나타남<h1>이 나타남
null일까?
null은 "값이 없다"는 뜻입니다. React는 이걸 특별하게 받아들여서, "지금은 렌더링할 게 없다"는 신호로 씁니다.
return null;은 곧 "이 컴포넌트는 지금 화면에 아무것도 그리지 않겠다"는 명확한 선언입니다.
undefined는 왜 안 될까요?
function Welcome() {
}
undefined를 반환합니다.
function test() {}
console.log(test()); // undefined
null을 요구합니다. return null;을 보면 "아, 이 개발자는 일부러 아무것도 안 그리려고 했구나"라고 React도, 코드를 읽는 사람도 바로 알 수 있습니다.
null 반환
null을 요구하는 이유가 바로 이겁니다 — "아무것도 하지 않겠다"는 의사조차 명확하게 표현하도록 만든 것이죠.
return <div>...</div> → 화면에 그린다return null → 화면에 아무것도 그리지 않는다 (의도적)return undefined (또는 return을 아예 빼먹음) → 의도가 불분명한, 좋지 않은 코드
null은 "그릴 것이 없다"는 걸 일부러 표현하는 특별한 반환값이라고 이해하면 됩니다.
리액트에는 아주 중요한 규칙이 하나 있어요. 컴포넌트 이름은 반드시 대문자로 시작해야 합니다. 왜냐하면 JSX에서 태그를 만났을 때, 리액트는 첫 글자로 그게 "내 컴포넌트"인지 "일반 HTML 태그"인지를 구분하기 때문이에요.
<Welcome /> → Welcome 함수 호출)<div>, <h1> 처럼)
그래서 <welcome />(소문자)라고 쓰면, 리액트는 이걸 존재하지도 않는 HTML 태그로 보고 우리가 만든 컴포넌트를 실행하지 않아요. 화면에 아무것도 안 나오죠.
function Welcome() {
return 안녕
;
}
// ✅ 대문자 → 내 컴포넌트 실행
// ❌ 소문자 → HTML 태그로 오해, 동작 안 함
UserCard, NavBar)로 씁니다.
컴포넌트를 재사용하려면, 상황마다 다른 데이터를 넣어줄 수 있어야 해요. 이때 쓰는 게 props(프롭스, properties)예요. 부모가 자식 컴포넌트를 쓸 때 HTML 속성처럼 값을 적어주면, 자식이 그 값을 받아 씁니다.
function Card(props) {
return <div className="card">{props.title}</div>;
}
2) 사용 — 같은 페이지에서 몇 번이든 (props만 다르게)
function App() {
return (
<div>
<Card title="사과" />
<Card title="바나나" />
<Card title="딸기" />
</div>
);
}
Card는 한 번만 만들었는데, App 안에서 <Card />를 세 번 썼어요. 그래서 화면엔 카드가 세 개 그려지죠. 각각 title이라는 props로 다른 값("사과·바나나·딸기")을 넣었으니 틀은 같아도 내용은 다르게 나와요. ← 이게 재사용이에요.<Card />를 원하는 만큼)import로 불러와서)// 부모: name 이라는 props에 "철수" 를 담아 내려줌
// 1. 자식 컴포넌트 정의 — name이라는 props를 받아서 화면에 씀
function Welcome({ name }) {
return <h1>안녕, {name}!</h1>;
}
// 2. 부모 컴포넌트 — 여기서 실제로 값을 "지정"함
function App() {
return (
<div>
<Welcome name="철수" />
<Welcome name="영희" />
</div>
);
}
안녕, 철수!
안녕, 영희!
name="철수", name="영희"처럼 부모(App)가 값을 정하는 지점이 핵심이에요. 같은 Welcome 컴포넌트인데, 부모가 넘겨주는 값에 따라 결과가 달라집니다. 즉 props의 값은 항상 "부모가 정한다"는 것을 보여주는 예시입니다.자식 컴포넌트는 이 값을 첫 번째 매개변수 props로 받아요. 전달한 값들은 모두 props 객체에 담겨 들어옵니다.
// 자식: props 객체로 받아서 props.name 으로 사용
function Welcome(props) {
return {props.name}
;
}
// 결과: 철수
props.name처럼 매번 props.를 붙이는 게 번거로우면, 매개변수 자리에서 바로 꺼내(구조 분해) 쓸 수 있어요. 실무에서 훨씬 많이 쓰는 방식이에요.
// 구조 분해 — { name } 으로 바로 꺼냄
function Welcome({ name }) {
return {name}
;
}
const { name } = 객체처럼 이름으로 바로 꺼내는 일반 JS 문법이에요. 그래서 리액트가 아닌 곳에서도 똑같이 쓰여요. 여기선 props 객체에서 name만 꺼내 쓴 것뿐이에요.| 구분 | 부모(전달) | 자식(받기) |
|---|---|---|
| 문법 | <Welcome name="철수" /> | function Welcome(props) |
| 사용 | 속성처럼 이름="값" | props.name |
| 구조 분해 | 동일 | function Welcome({ name }) → name |
아주 중요한 규칙이에요. 자식 컴포넌트는 부모에게 받은 props를 마음대로 바꾸면 안 됩니다. props는 읽기 전용(read-only)이에요. 부모가 준 데이터를 자식이 몰래 바꿔버리면, 데이터가 어디서 어떻게 변했는지 추적하기가 지옥이 되니까요.
function Welcome({ name }) {
name = "영희"; // ❌ 절대 금지! props를 바꾸면 안 됨
return {name}
;
}
지금까지는 <Welcome name="철수" />처럼 속성(attribute)으로 값을 넘겼어요. 그런데 컴포넌트를 여는 태그와 닫는 태그로 감싸서 그 사이에 내용을 넣으면, 그 내용이 props.children이라는 특별한 props로 전달돼요.
children은 리액트에서 가장 쉬운 개념 중 하나예요. HTML부터 보면 바로 감이 와요.<div>
안녕하세요
</div>
여기서 안녕하세요는 div 태그 안에 든 내용이죠. 이렇게 태그도 넣을 수 있고요:
<div>
<h1>제목</h1>
<p>본문</p>
</div>
2) 리액트도 똑같아요Card 컴포넌트를 이렇게 태그로 감싸서 쓰면:
<Card>
안녕하세요
</Card>
이 안녕하세요를 리액트는 어디에 담을까요? → 자동으로 props.children에 넣어줘요.Card를 이렇게 호출한다고 생각하면 딱 맞아요:
Card({
children: "안녕하세요"
});
그래서 우리는 이렇게 받아서 원하는 자리에 꽂아요:
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
그러면 {children} 자리에 안녕하세요가 들어가서 →
<div className="card">
안녕하세요
</div>
가 돼요.<Card>
<h1>제목</h1>
<p>본문</p>
</Card>
이러면 children은 그 안의 <h1>·<p> 전부가 돼요.Card 입장에서 h1과 p는 자식(child)이에요. 그래서 이름이 children인 거죠.Card는 "안에 뭐가 들었는지는 몰라" 하면서도, 받은 걸 그대로 자기 상자 안에 넣어줘요. 글자·그림·버튼·다른 컴포넌트 뭐든 가능해요. 즉 "난 껍데기(스타일·구조)만 만들 테니, 알맹이는 네가 넣어" 하는 거예요.name은 태그 안의 속성으로 전달 → <Welcome name="철수" /> 하면 props.name === "철수"children은 여는·닫는 태그 사이의 내용으로 전달 → <Card>안녕하세요</Card> 하면 props.children === "안녕하세요"
// 사용: 태그 사이에 "내용" 을 넣음
<Card>내용</Card>
// 정의: props.children 으로 그 "내용" 을 받음
function Card({ children }) {
return <div className="card">{children}</div>;
}
// 결과: <div class="card">내용</div>
children으로 자유롭게 끼워 넣습니다.
즉 children은 "태그 사이에 뭐가 오든 그대로 받아 그 자리에 그려주는" props예요.
children에는 글자뿐 아니라 다른 태그·컴포넌트도 통째로 넣을 수 있어요.
<Card><h1>제목</h1><Welcome name="철수" /></Card> — 이렇게 감싼 것 전부가 children으로 들어갑니다.
name="철수", 받을 땐 props.name 또는 구조 분해 { name }.props.children으로 전달돼요.