컴포넌트란 대문자 규칙 props 읽기 전용 children 📝 문제풀이
◀ 이전 📋 목차 CH 04 ▶
🧱 CHAPTER 03 · 입문

컴포넌트로 조립하고, Props로 소통하기

리액트의 심장은 컴포넌트예요. 화면을 작은 부품으로 쪼개 재사용하고, 그 부품끼리는 props라는 통로로 데이터를 주고받아요. 이 두 개념만 잡으면 리액트가 갑자기 쉬워집니다.

🎯 이 장을 끝내면
🧱
컴포넌트란
화면을 독립·재사용 가능한 부품으로
레고 블록을 조립하듯 UI를 만들어요.

컴포넌트(Component)독립적이고 재사용 가능한 UI 조각(부품)이에요. 버튼 하나, 카드 하나, 헤더 하나가 각각 컴포넌트가 될 수 있어요. 이 부품들을 레고처럼 조립해서 페이지 전체를 만듭니다.

우리가 배우는 방식은 함수 컴포넌트예요. 말 그대로 "대문자로 시작하는 함수가 JSX를 return"하면 그게 컴포넌트예요. 정말 이게 전부입니다.

// 함수 컴포넌트 정의 — 대문자 Welcome, JSX를 return
function Welcome() {
  return 

안녕

; } // 사용 — HTML 태그처럼 로 씀
🧩 함수 컴포넌트의 3가지 조건 🟪 리액트 이름이 대문자로 시작한다.   그냥 함수다.   JSX를 return한다.
이 셋만 만족하면 리액트는 그걸 컴포넌트로 인식하고, <Welcome />처럼 태그로 사용할 수 있어요. "화면의 한 조각을 만드는 함수" — 그게 컴포넌트예요. 한 번 만들면 여러 곳에서 몇 번이든 재사용합니다.
컴포넌트 함수 컴포넌트 재사용 return JSX props children 읽기 전용 단방향
🧭
큰 그림. 페이지 = 여러 컴포넌트의 조립. 각 컴포넌트는 자기 화면 조각을 return하고, 부모는 자식에게 props로 데이터를 내려줘요. 이번 장은 이 "조립"과 "데이터 전달"을 다룹니다.
🖼️ 그림으로 보기 — 컴포넌트 트리 & props는 아래로
App Header ProductList Card Card props ↓ props ↓ 데이터(props)는 항상 부모 → 자식 (아래로만) 흘러요
페이지는 컴포넌트들의 트리예요. 부모가 자식에게 props로 데이터를 내려주고, 그 방향은 항상 아래(부모→자식) 한 방향이에요. 이게 리액트의 단방향 데이터 흐름이고, 그래서 데이터가 어디서 왔는지 추적하기 쉬워요.
🧩 컴포넌트는 React의 개념입니다 — 펼쳐 보기
결론부터: 컴포넌트React의 개념입니다
ReactJavaScript를 기반으로 만든 라이브러리이기 때문에, 컴포넌트를 만들 때는 JavaScript 문법을 사용하지만, "컴포넌트"라는 개념 자체는 React가 만든 것입니다.

예를 들어:
function Welcome() {
      return <h1>안녕하세요!</h1>;
    }

📍 이 코드를 뜯어보면:
  • function Welcome()JavaScript 함수
  • returnJavaScript 문법
  • <h1>안녕하세요!</h1>JSX (React의 문법)
  • 이 Welcome을 나중에 <Welcome />처럼 쓰는 것 → React 컴포넌트로서 사용하는 것

🧠 핵심 한 줄 요약
컴포넌트 = "UI를 반환하는 JavaScript 함수"

즉, JavaScript 함수의 특별한 "사용 방식"이지, 새로운 문법이 아닙니다.

일반 JavaScript 함수는 값(value)을 리턴합니다.
function add(a, b) {
      return a + b; // 숫자를 계산해서 값을 리턴
    }
React에서는:
function Button() {
      return <button>클릭</button>; // 숫자가 아니라 화면(UI)을 리턴
    }
같은 "함수"라는 재료를 쓰지만, 리턴하는 게 값이냐 UI냐가 다릅니다. ReactUI를 리턴하는 이런 함수들을 컴포넌트라고 부르기로 정한 것입니다.
여기에 조금 더해서, 컴포넌트는 레고 블록이라고 생각하면 편합니다.

블록 하나하나 = 컴포넌트 (Header, Button, Footer)
블록을 조립한 완성품 = 전체 앱 (App)
function App() {
      return (
        <div>
          <Header />
          <Button />
          <Footer />
        </div>
      );
    }
작은 컴포넌트들을 조립해서 큰 화면을 만드는 것 — 이게 React의 핵심 철학인 "컴포넌트 기반 설계"입니다.

JavaScript가 제공하는 것 vs React가 제공하는 것
JavaScript가 제공하는 것 React가 제공하는 것
변수컴포넌트
함수JSX
객체props
배열state
클래스Hooks (useState, useEffect 등)
모듈(import/export)
📦 모듈(import/export)이란? — 펼쳐 보기
JavaScript에서 코드를 여러 파일로 나눠서 관리할 수 있게 해주는 기능입니다.

왜 필요한가
파일 하나에 코드를 다 몰아넣으면 관리가 힘드니까, 기능별로 파일을 나누고 필요한 것만 갖다 쓰는 겁니다.

사용 예시
math.js (기능을 만들어서 "내보내는" 파일)
function add(a, b) {
  return a + b;
}

export default add; // 이 함수를 다른 파일에서 쓸 수 있게 내보냄

app.js (그 기능을 "가져와서" 쓰는 파일)
import add from './math.js'; // math.js에서 add 함수를 가져옴

console.log(add(2, 3)); // 5

  • export = "이 코드, 다른 파일에서도 쓸 수 있게 내보낼게"
  • import = "다른 파일에 있는 그 코드, 여기로 가져올게"

React와의 연결점
React 공부하시면 파일 맨 위에 이런 코드를 자주 보게 되실 거예요:
import React from 'react';
import Header from './Header'; // 내가 만든 Header 컴포넌트 가져오기

function App() {
  return <Header />;
}

export default App; // App 컴포넌트를 다른 곳에서 쓸 수 있게 내보내기

컴포넌트를 여러 파일로 나눠서 관리할 때 필수적으로 쓰는 문법이 바로 이 import/export입니다.
import/export는 React가 만든 게 아니라 순수 JavaScript 자체의 기능이기 때문에, 표에서 "모듈(import/export)"이 JavaScript 쪽에만 있고 React 쪽이 비어있습니다
▲ 접기
🍳 함수가 "컴포넌트"로 인정받으려면 지켜야 할 규칙
1. 함수 이름은 대문자로 시작 (Welcome, Button ○ / welcome, button ✗)
— 소문자면 React가 그냥 HTML 태그로 착각합니다.
2. JSX(또는 null)를 리턴해야 합니다.

이 두 가지만 지키면 그냥 함수가 아니라 "React가 알아보는 컴포넌트"가 됩니다.
📦 함수가 컴포넌트로 인정받으려면 왜 JSX 또는 null을 리턴해야할까? — 펼쳐 보기
결론부터 말하면, null"아무것도 그리지 마라"는 뜻입니다.

React에서 컴포넌트는 결국 "화면에 무엇을 보여줄지"를 반환해야 합니다. 보통은 JSX를 반환합니다.
function Welcome() {
          return <h1>안녕하세요</h1>;
        }
그러면 화면에 <h1>이 그려집니다.

그런데 아예 아무것도 보여주고 싶지 않을 때도 있습니다. 예를 들어, 로그인한 사용자에게만 보여주는 컴포넌트를 만든다고 해봅시다.
function Secret({ isLogin }) {
          if (!isLogin) {
            return null;
          }

          return <h1>비밀 페이지</h1>;
        }
  • 로그인 안 함 → null 반환 → 화면에 아무것도 안 나타남
  • 로그인 함 → JSX 반환 → 화면에 <h1>이 나타남
왜 하필 null일까?

JavaScript에서 null은 "값이 없다"는 뜻입니다. React는 이걸 특별하게 받아들여서, "지금은 렌더링할 게 없다"는 신호로 씁니다.

즉, return null;은 곧 "이 컴포넌트는 지금 화면에 아무것도 그리지 않겠다"는 명확한 선언입니다.
그럼 undefined는 왜 안 될까요?

만약 이렇게 함수를 비워두면
function Welcome() {
        }
리턴이 없으니 JavaScript는 자동으로 undefined를 반환합니다.
function test() {}
        console.log(test()); // undefined
문제는 여기서 생깁니다. React 입장에서는 이게 "실수로 return을 빼먹은 건지", 아니면 "진짜 아무것도 그리려는 게 아닌지" 구분할 방법이 없습니다.

그래서 React는 의도를 명확히 표현하라는 의미로 null을 요구합니다. return null;을 보면 "아, 이 개발자는 일부러 아무것도 안 그리려고 했구나"라고 React도, 코드를 읽는 사람도 바로 알 수 있습니다.
🎭 비유로 이해하기

컴포넌트를 배우라고 생각해보세요. 감독(React)이 배우에게 "무대에 나가서 연기해"라고 지시합니다. 배우가 할 수 있는 반응은 이렇습니다.

  • 🎭 연기한다 → JSX 반환
  • 🚪 오늘은 무대에 안 나온다 → null 반환

그런데 배우가 아무 대답도 안 하면(undefined), 감독은 "실수한 건가? 대사를 못 받은 건가?" 하고 헷갈리게 됩니다. React가 null을 요구하는 이유가 바로 이겁니다 — "아무것도 하지 않겠다"는 의사조차 명확하게 표현하도록 만든 것이죠.
👉 정리

  • return <div>...</div> → 화면에 그린다
  • return null → 화면에 아무것도 그리지 않는다 (의도적)
  • return undefined (또는 return을 아예 빼먹음) → 의도가 불분명한, 좋지 않은 코드

그래서 "React 컴포넌트는 JSX 또는 null을 반환한다"고 배우는 겁니다. null"그릴 것이 없다"는 걸 일부러 표현하는 특별한 반환값이라고 이해하면 됩니다.
▲ 접기
👉 정리
JavaScript 함수 → React에서는 컴포넌트로 사용될 수 있다.
처음엔 "컴포넌트 = 화면을 만들어 반환하는, React 방식으로 다뤄지는 JavaScript 함수"라고 이해하면 거의 맞습니다.
지금 배우는 순서가 컴포넌트 → JSX → Props → State라면, React의 가장 핵심적인 개념으로 들어가는 단계입니다. 여기가 이해되면 이후 내용은 훨씬 수월해질 거예요.
🔠
대문자 규칙
이름은 반드시 대문자로 시작
소문자로 시작하면 컴포넌트가 아니라 HTML 태그로 취급돼요.

리액트에는 아주 중요한 규칙이 하나 있어요. 컴포넌트 이름은 반드시 대문자로 시작해야 합니다. 왜냐하면 JSX에서 태그를 만났을 때, 리액트는 첫 글자로 그게 "내 컴포넌트"인지 "일반 HTML 태그"인지를 구분하기 때문이에요.

🔍 리액트가 첫 글자로 판단하는 방식 🟪 리액트 대문자로 시작 → 내가 만든 컴포넌트로 취급 (<Welcome />Welcome 함수 호출)
소문자로 시작 → 브라우저 기본 HTML 태그로 취급 (<div>, <h1> 처럼) 그래서 <welcome />(소문자)라고 쓰면, 리액트는 이걸 존재하지도 않는 HTML 태그로 보고 우리가 만든 컴포넌트를 실행하지 않아요. 화면에 아무것도 안 나오죠.
function Welcome() {
  return 

안녕

; } // ✅ 대문자 → 내 컴포넌트 실행 // ❌ 소문자 → HTML 태그로 오해, 동작 안 함
⚠️
기억하세요. 컴포넌트 이름은 무조건 대문자로 시작. 여러 단어면 파스칼 케이스(예: UserCard, NavBar)로 씁니다.
📌 "CH 05 · 이벤트 처리"에 카멜 케이스 속성에 대한 자세한 설명이 있습니다.
📦
props
부모가 자식에게 데이터를 내려주는 통로
HTML 속성처럼 생겼지만, 컴포넌트에게 값을 전달해요.

컴포넌트를 재사용하려면, 상황마다 다른 데이터를 넣어줄 수 있어야 해요. 이때 쓰는 게 props(프롭스, properties)예요. 부모가 자식 컴포넌트를 쓸 때 HTML 속성처럼 값을 적어주면, 자식이 그 값을 받아 씁니다.

🍪 그런데 "컴포넌트를 재사용"한다는 게 정확히 뭘까요? — 펼쳐 보기
컴포넌트의 핵심은 이거예요 —
"선언(정의)은 한 번, 사용은 여러 번."

🍪 쿠키 틀로 생각해요
쿠키 을 하나 만들어두면(=컴포넌트 선언), 그 틀로 쿠키를 여러 개 찍어낼 수 있죠(=컴포넌트 사용). 틀은 하나지만 쿠키는 몇 개든 나와요. 그리고 props가 바로 "이번엔 어떤 맛으로 찍을까"를 정해주는 값이에요.

1) 선언 — 딱 한 번
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로 불러와서)

그래서 버튼·카드·헤더처럼 반복되는 화면 조각을 한 번만 만들어두고 계속 재사용해요. 매번 똑같은 HTML을 복사·붙여넣기 할 필요가 없어지는 게 컴포넌트의 가장 큰 장점이에요.
👉 한 문장 정리: 컴포넌트는 한 번 선언하면 같은 페이지든 다른 페이지든 몇 번이고 갖다 쓸 수 있고, 그때마다 props로 내용만 바꿔 낍니다.
▲ 접기
// 부모: 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}

; } // 결과:

철수

✂️ 구조 분해(destructuring) — 더 깔끔하게 🟦 순수 JS 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 객체(또는 구조 분해)로.
🔒
읽기 전용
props는 읽기 전용(immutable)
자식은 받은 props를 절대 바꾸면 안 돼요.

아주 중요한 규칙이에요. 자식 컴포넌트는 부모에게 받은 props를 마음대로 바꾸면 안 됩니다. props는 읽기 전용(read-only)이에요. 부모가 준 데이터를 자식이 몰래 바꿔버리면, 데이터가 어디서 어떻게 변했는지 추적하기가 지옥이 되니까요.

function Welcome({ name }) {
  name = "영희";   // ❌ 절대 금지! props를 바꾸면 안 됨
  return 

{name}

; }
🔗 단방향 데이터 흐름과 연결 🟪 리액트 CH01에서 배운 단방향 데이터 흐름 기억하죠? 데이터는 부모 → 자식 한 방향으로만 흘러요. props가 읽기 전용인 것도 같은 이유예요. 자식이 props를 못 바꾸니, "이 데이터는 부모가 준 거구나" 하고 흐름이 명확해집니다. 그럼 값을 바꿔야 할 땐 어떻게 하냐고요? 컴포넌트 자기 안에서 바뀌는 값이 필요하면 state를 써요. 바로 다음 장(CH04)의 주제입니다.
🚫
규칙. props = 읽기만. 바꿔야 하는 값이라면 그건 props가 아니라 state의 영역이에요. props를 직접 수정하는 코드는 리액트에서 버그의 원인이 됩니다.
👶
children
태그 사이의 내용은 props.children
여는 태그와 닫는 태그 사이에 넣은 것이 통째로 전달돼요.

지금까지는 <Welcome name="철수" />처럼 속성(attribute)으로 값을 넘겼어요. 그런데 컴포넌트를 여는 태그와 닫는 태그로 감싸서사이에 내용을 넣으면, 그 내용이 props.children이라는 특별한 props로 전달돼요.

🎁 children, 이해 안 되면 여기 — "리액트가 속으로 어떻게 부르는지"까지 차근차근 — 펼쳐 보기
사실 children은 리액트에서 가장 쉬운 개념 중 하나예요. HTML부터 보면 바로 감이 와요.

1) 먼저 HTML
<div>
  안녕하세요
</div>
여기서 안녕하세요div 태그 안에 든 내용이죠. 이렇게 태그도 넣을 수 있고요:
<div>
  <h1>제목</h1>
  <p>본문</p>
</div>
2) 리액트도 똑같아요
Card 컴포넌트를 이렇게 태그로 감싸서 쓰면:
<Card>
  안녕하세요
</Card>
안녕하세요를 리액트는 어디에 담을까요? → 자동으로 props.children에 넣어줘요.

3) 리액트는 "속으로" 이렇게 불러요
눈에 안 보이는 곳에서 리액트가 Card를 이렇게 호출한다고 생각하면 딱 맞아요:
Card({
  children: "안녕하세요"
});
그래서 우리는 이렇게 받아서 원하는 자리에 꽂아요:
function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}
그러면 {children} 자리에 안녕하세요가 들어가서 →
<div className="card">
  안녕하세요
</div>
가 돼요.

4) 글자만 되는 게 아니에요
태그·컴포넌트도 통째로 들어가요.
<Card>
  <h1>제목</h1>
  <p>본문</p>
</Card>
이러면 children은 그 안의 <h1>·<p> 전부가 돼요.

🌳 왜 이름이 "children"일까?
위 구조를 트리로 그리면 이래요:
Card ├── h1 └── p
Card 입장에서 h1p자식(child)이에요. 그래서 이름이 children인 거죠.

🎁 선물상자 비유
Card"안에 뭐가 들었는지는 몰라" 하면서도, 받은 걸 그대로 자기 상자 안에 넣어줘요. 글자·그림·버튼·다른 컴포넌트 뭐든 가능해요. 즉 "난 껍데기(스타일·구조)만 만들 테니, 알맹이는 네가 넣어" 하는 거예요.

🌟 가장 중요한 한 줄 — name vs children
  • 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으로 자유롭게 끼워 넣습니다. children은 "태그 사이에 뭐가 오든 그대로 받아 그 자리에 그려주는" props예요.
➕ children에는 텍스트만? 아니에요
children에는 글자뿐 아니라 다른 태그·컴포넌트도 통째로 넣을 수 있어요.
예: <Card><h1>제목</h1><Welcome name="철수" /></Card> — 이렇게 감싼 것 전부가 children으로 들어갑니다.
🧠 이 장 핵심 요약
📝
문제풀이 · 점검
배운 걸 가볍게 점검해봐요
시험이 아니라 "내가 이해했나" 확인용이에요. 틀려도 바로 해설이 나와요.
🧪
문제를 풀면 즉시 정답과 해설이 나오고, 위쪽 바에 점수가 쌓여요. 네 가지 유형(객관식 · O/X · 빈칸 · 개념결과)을 섞어 두었어요.
CH 04 🎚️ State와 useState — 컴포넌트 안에서 바뀌는 값 다루기