LogoSEO Jing
  • All Posts
  • SEO Jing
  • KD Team
  • CLab CoreTeam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

프론트엔드스터디React컴포넌트JSXpropsuseState

프론트엔드 스터디 10주차: React 기초 1 — 컴포넌트, JSX, props, useState

2026년 6월 1일·25분 읽기

10주차 학습 자료

  • 학습 챕터: 한 입 크기로 잘라먹는 리액트 — 컴포넌트 / JSX / Props / 이벤트 처리하기 / 컴포넌트와 상태
  • 자료 바로가기:
    1. 컴포넌트
    1. JSX
    1. 컴포넌트에 값 전달하기 (Props)
    1. 이벤트 처리하기
    1. 컴포넌트와 상태 (useState)

안녕하세요. 10주차입니다. 드디어 React를 시작합니다. 지난 주까지 배운 map, 구조 분해, 스프레드, 모듈 같은 ES6+ 문법이 이번 주부터 실제 React 코드에서 어떻게 쓰이는지 직접 확인하게 됩니다. 이번 주는 React의 가장 기본이 되는 네 가지 — 컴포넌트, JSX, props, useState를 다룹니다. 이 네 가지만 이해하면 간단한 인터랙티브 앱을 만들 수 있습니다.


스킵 진단 문제

아래 8문제를 막힘없이 풀 수 있다면,

이번 주 학습 자료를 생략하거나, 모르는 부분만 선택적으로 읽으셔도 좋습니다.

바로 하단의 본문 섹션들을 확인해보세요.

Quiz1 / 8
Q.React에서 컴포넌트 이름은 왜 대문자로 시작해야 하나요?

1. React란? — 레고 블록으로 UI 만들기

React는 Facebook(Meta)이 만든 UI 라이브러리입니다. 웹 페이지의 화면을 작은 조각(컴포넌트)으로 나누어 조립하는 방식으로 개발합니다. 레고 블록처럼 작은 부품을 만들어 놓고, 조합해서 복잡한 화면을 완성하는 것이 핵심 아이디어입니다.

React의 세 가지 특징

1. 선언적(Declarative) — "어떻게"가 아니라 "무엇을" 보여줄지만 작성합니다.

js
// 명령형 — DOM을 직접 조작 (jQuery 스타일)
const button = document.createElement("button");
button.textContent = "클릭";
button.addEventListener("click", () => {
  button.textContent = "클릭됨!";
});
document.body.appendChild(button);

// 선언형 — React 스타일: "이런 화면을 보여줘"
function App() {
  const [clicked, setClicked] = useState(false);

명령형은 "버튼을 만들고, 텍스트를 넣고, 이벤트를 달고..."처럼 순서대로 지시합니다. 선언형은 "상태가 이러면 화면은 이렇게 보여줘"라고 결과만 선언합니다. 카페에서 "아메리카노 한 잔 주세요"라고 말하는 것과 같습니다. 원두를 갈고 물을 끓이는 과정은 알아서 처리됩니다.

2. 컴포넌트 기반(Component-Based) — UI를 독립적인 조각으로 나눕니다.

jsx
// Header, Sidebar, Content 각각이 독립적인 컴포넌트
function App() {
  return (
    <div>
      <Header />
      <Sidebar />
      <Content />
    </div>
  );
}

3. 단방향 데이터 흐름(One-way Data Flow) — 데이터는 항상 부모에서 자식으로 흐릅니다. 자식이 부모의 데이터를 직접 바꿀 수 없습니다. 이 덕분에 데이터가 어디서 변경되었는지 추적하기 쉽습니다.


2. 컴포넌트 — React의 기본 단위

React에서 컴포넌트는 UI 조각을 반환하는 JavaScript 함수 입니다. 버튼 하나, 카드 하나, 페이지 전체 모두 컴포넌트가 될 수 있습니다.

jsx
// 가장 간단한 컴포넌트
function Greeting() {
  return <h1>안녕하세요!</h1>;
}

// 다른 컴포넌트 안에서 사용
function App() {
  return (
    <div>
      <Greeting />
      <Greeting />
    </div>
  );
}

컴포넌트 이름은 반드시 대문자

React(정확히는 JSX)는 대문자로 시작하면 컴포넌트, 소문자로 시작하면 HTML 태그로 구분합니다. 이 규칙을 어기면 컴포넌트가 렌더링되지 않습니다.

jsx
// ✅ 대문자 — React 컴포넌트로 인식
<MyButton />   → React.createElement(MyButton)

// ❌ 소문자 — HTML 태그로 인식 (존재하지 않는 태그)
<myButton />   → React.createElement('mybutton')

3. JSX — JavaScript 안에서 HTML 작성하기

JSX는 JavaScript 안에서 HTML과 비슷한 문법으로 UI를 작성할 수 있게 해주는 문법 확장입니다. 브라우저가 직접 이해하는 것이 아니라, 빌드 과정에서 JavaScript 함수 호출로 변환됩니다.

JSX 핵심 규칙

규칙 1. 하나의 루트 요소로 감싸기
jsx
// ❌ 에러 — 루트 요소가 두 개
function App() {
  return (
    <h1>제목</h1>
    <p>내용</p>
  );
}

// ✅ Fragment로 감싸기 — 불필요한 DOM 노드 없음
function App() {
  return (
    <>
      <h1>제목</h1>
      <p>내용
규칙 2. 안에 JavaScript 표현식 사용
jsx
function Profile({ user }) {
  return (
    <div>
      {/* 변수 */}
      <h1>{user.name}</h1>

      {/* 계산 */}
      <p>나이: {user.age + 1}살 (내년 기준)</p>

      {/* 함수 호출 */}

규칙 3. class 대신 className — class는 JavaScript 예약어이기 때문입니다.

jsx
// ❌ HTML 습관대로 쓰면 안 됨
<div class="container">

// ✅ JSX에서는 className
<div className="container">

규칙 4. 조건부 렌더링 — if문은 표현식이 아니므로 안에 쓸 수 없습니다. 대신 && 연산자와 삼항 연산자를 사용합니다.

jsx
function Dashboard({ isLoggedIn, notifications }) {
  return (
    <div>
      {/* && — 조건이 true일 때만 렌더링 */}
      {isLoggedIn && <p>환영합니다!</p>}

      {/* 삼항 — true/false에 따라 다른 것을 렌더링 */}
      {isLoggedIn ? <LogoutButton /> : <LoginButton />}

      {/* 숫자 0 주의 — 0은 falsy이지만 화면에 0이 출력됨 */

4. props — 부모가 자식에게 데이터 전달하기

props(properties)는 부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달하는 방법입니다. HTML 속성을 쓰듯이 전달하고, 자식은 함수의 매개변수처럼 받습니다.

jsx
// 부모에서 props 전달
function App() {
  return <UserCard name="철수" age={25} isAdmin={true} />;
}

// 자식에서 구조 분해로 수신 — 지난 주 배운 문법!
function UserCard({ name, age, isAdmin }) {
  return (
    <div>
      <h2>{name}</h2

props는 읽기 전용

props는 부모가 준 데이터이므로, 자식에서 절대 수정하면 안 됩니다. 물건을 빌려 쓰는 것과 같습니다 — 빌린 물건을 마음대로 바꾸면 안 되겠죠. 데이터를 바꿔야 한다면 부모에서 변경해야 합니다.

jsx
// ❌ props 직접 수정 — 절대 하면 안 됨
function UserCard({ name }) {
  name = "영희"; // React의 단방향 원칙 위반
  return <h2>{name}</h2>;
}

리스트 렌더링과 key prop

배열 데이터를 화면에 렌더링할 때 map()을 사용합니다. 이때 각 요소에 key prop을 반드시 전달해야 합니다. key는 React가 어떤 요소가 변경/추가/삭제되었는지 효율적으로 판단하는 데 사용하는 고유 식별자입니다.

jsx
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        // key는 고유한 id를 사용 — 배열 인덱스는 피하세요
        <li key={todo.id}>
          {todo.text} {todo.done && "✓"}
        </li>
      ))}

key에 배열 인덱스를 쓰면 요소가 추가/삭제/재정렬될 때 React가 헷갈립니다. 데이터의 고유한 id를 사용하는 것이 올바른 방법입니다.


5. useState — 컴포넌트가 기억하는 값

일반 JavaScript 변수는 컴포넌트가 리렌더링될 때마다 초기화됩니다. 버튼을 클릭해서 숫자를 1 올려도, 리렌더링 되면 다시 0으로 돌아갑니다. useState는 렌더링 사이에도 값을 유지하는 React의 방법입니다.

기본 사용법

jsx
import { useState } from "react";

function Counter() {
  // [현재 값, 값을 바꾸는 함수] = useState(초기값)
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>현재 카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}

useState는 배열을 반환합니다. 지난 주 배운 배열 구조 분해로 첫 번째 요소(현재 값)와 두 번째 요소(변경 함수)를 받습니다. 변수 이름은 자유롭게 지정할 수 있지만, [something, setSomething] 패턴이 관례입니다.

왜 setState로만 변경해야 하나요?

jsx
// ❌ 직접 변경 — React가 변경을 감지하지 못함
count = count + 1;

// ✅ setState — React가 감지하고 리렌더링 실행
setCount(count + 1);

React는 setState가 호출될 때 상태가 바뀌었다는 것을 인식하고, 컴포넌트를 다시 실행(리렌더링)합니다. 변수를 직접 바꾸면 React가 모르기 때문에 화면이 업데이트되지 않습니다.

이전 상태 기반 업데이트 — 함수형 업데이트

상태를 이전 값 기준으로 변경할 때는 함수형 업데이트 패턴을 사용하는 것이 안전합니다.

jsx
// ❌ 위험 — 빠른 연속 클릭 시 오래된 count를 참조할 수 있음
setCount(count + 1);

// ✅ 안전 — 항상 최신 상태(prev)를 받아서 계산
setCount((prev) => prev + 1);

왜 이런 차이가 생길까요? React는 setState 호출을 모아서 한 번에 처리(배칭)할 수 있습니다. 이때 count는 현재 렌더링 시점의 값이므로 여러 번 호출해도 같은 결과가 나올 수 있습니다. 하지만 함수형 업데이트는 항상 직전 상태를 인자로 받으므로 안전합니다.

jsx
// 버튼 한 번 클릭에 +3을 하고 싶은 경우
function handleClick() {
  // ❌ 세 번 호출해도 +1만 됨 (모두 같은 count를 참조)
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);

  // ✅ 세 번 호출하면 +3이 됨 (각각 최신 prev를 받음)
  setCount((prev) => prev + 1);
  setCount((prev) => prev + 1);
  setCount((prev) => prev + 1);
}

여러 개의 상태 관리

하나의 컴포넌트에서 여러 상태가 필요하면 useState를 여러 번 호출합니다.

jsx
function SignupForm() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [agreed, setAgreed] = useState(false);

  return (
    <form>
      <input value={name} onChange={(e) => setName(e 

📝 학습 이해 퀴즈

아래 코드를 읽고 결과를 예측해보세요. 가능하면 직접 실행하지 말고 먼저 머릿속으로 생각해본 뒤 확인하세요.

Quiz1 / 2
Q.아래 컴포넌트에서 버튼을 3번 클릭하면 화면에 표시되는 숫자는 무엇인가요?
js
function Counter() {
const [count, setCount] = useState(0);

function handleClick() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}

return (

<div>
<p>{count}</p>
<button onClick={handleClick}>+3?</button>
</div>
);
}

📝 10주차 스터디 인증 미션

아래 문제를 직접 코드로 작성해보고, 코드와 함께 댓글로 남겨주세요.

Quiz1 / 3
Q.아래 요구사항을 만족하는 Counter 컴포넌트를 만들어보세요. (1) + 버튼을 누르면 숫자가 1 올라감 (2) - 버튼을 누르면 숫자가 1 내려감 (3) 초기값은 0 (4) 함수형 업데이트(prev => ...)를 사용할 것

다음 주 안내

다음 주에는 useEffect, 이벤트 처리, 폼 상태 관리를 다룹니다. 이번 주에 배운 useState와 함께 사용하면 API에서 데이터를 가져오거나, 사용자 입력을 처리하는 실제 앱에 가까운 코드를 작성할 수 있게 됩니다.


관련 포스팅

  • 프론트엔드 스터디 9주차 학습 자료: React 입문 전 필수 JS 문법

포스트 목록

/study/clab-26-1
파일 13개, 폴더 2개
프론트엔드 스터디 1주차: 마크업 그 이상, 실무를 위한 중급 HTML 가이드프론트엔드 스터디 2주차: 사용자와 소통하는 폼 & CSS의 시작프론트엔드 스터디 3주차: 프론트엔드의 첫 번째 벽, 박스 모델과 스타일링프론트엔드 스터디 4주차: 자유자재 레이아웃 (포지션과 플렉스박스)프론트엔드 스터디 5주차: JavaScript 시작 — 타입, 변수, 그리고 JS가 이상한 이유프론트엔드 스터디 6주차: 객체, 함수, 그리고 스코프프론트엔드 스터디 7주차: 불변성, 프로토타입, 타입 체크프론트엔드 스터디 8주차: 클로저, Promise, async/await프론트엔드 스터디 9주차: React 입문 전 필수 JS 문법 — map, 구조 분해, 스프레드프론트엔드 스터디 10주차: React 기초 1 — 컴포넌트, JSX, props, useState프론트엔드 스터디 11주차: React 기초 2 — useEffect, 이벤트 처리, 폼프론트엔드 스터디 심화: this, 실행 컨텍스트, 이터러블프론트엔드 스터디 심화: 에러 처리와 정규 표현식
return (
<button onClick={() => setClicked(true)}>
{clicked ? "클릭됨!" : "클릭"}
</button>
);
}
</p>
</>
);
}
<p>가입일: {formatDate(user.createdAt)}</p>
</div>
);
}
}
{notifications.length > 0 && <Badge count={notifications.length} />}
</div>
);
}
>
<p>{age}세</p>
{isAdmin && <span>관리자</span>}
</div>
);
}
</ul>
);
}
// 사용
<TodoList
todos={[
{ id: 1, text: "장보기", done: true },
{ id: 2, text: "운동하기", done: false },
{ id: 3, text: "공부하기", done: false },
]}
/>;
>
+1
</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(0)}>초기화</button>
</div>
);
}
.
target
.
value
)
}
/>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<label>
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>
동의합니다
</label>
</form>
);
}