Fullstack 83

heo4·2026년 8월 12일

Fullstack

목록 보기
38/70

풀스택

⚛️ React 컴포넌트와 props 정리

React의 핵심 개념인 컴포넌트와 props를 정리했다. 컴포넌트를 왜 나누는지부터, props로 데이터를 전달하는 방법, 구조 분해 할당, children props까지 다룬다.

1. 컴포넌트(Component)란 무엇인가

컴포넌트는 "화면의 한 조각을 만드는 함수"다. 지금까지 계속 써온 App도 사실 하나의 컴포넌트였다.

컴포넌트는 이렇게 정의한다.

  • 이름은 반드시 대문자로 시작한다. (app X, App O)
  • 함수 안에서 JSX를 return한다.
function Welcome() {
  return <h2>환영합니다!</h2>;
}

function App() {
  return (
    <div>
      <h1>나의 첫 컴포넌트 나누기</h1>
      <Welcome />
      <Welcome />
      <Welcome />
    </div>
  );
}

export default App;

<Welcome />처럼 태그 형태로 컴포넌트를 3번 사용하면, "환영합니다!"가 화면에 3번 출력된다.

컴포넌트 이름은 왜 꼭 대문자로 시작해야 할까? JSX는 태그 이름의 첫 글자로 "이게 원래 있던 HTML 태그인지, 내가 만든 컴포넌트인지"를 구분한다. <welcome />처럼 소문자로 쓰면 JSX는 이걸 <div>, <span>처럼 브라우저가 원래 알고 있는 HTML 태그로 착각해서 찾다가 오류를 낸다. 그래서 직접 만든 컴포넌트는 항상 대문자로 시작해야 한다.


2. 왜 컴포넌트를 나누는가

방금 예시처럼 Welcome을 한 번만 만들어두면 몇 번이든 재사용할 수 있다. 만약 컴포넌트로 나누지 않았다면 <h2>환영합니다!</h2>를 필요할 때마다 똑같이 복사-붙여넣기 해야 했을 것이다.

  • 재사용성: 한 번 만든 컴포넌트를 여러 곳에서 반복 사용할 수 있다.
  • 관심사 분리: 화면을 작은 단위로 나누면, 버튼 하나를 고칠 때 버튼 컴포넌트 코드만 보면 되기 때문에 전체 코드를 뒤질 필요가 없다.

컴포넌트를 안 나누고 App 하나에 다 몰아넣으면 왜 안 될까? 처음에는 코드가 짧아서 괜찮아 보이지만, Todo 앱처럼 기능이 늘어나면 App 함수 하나가 수백 줄이 되어버린다. 그러면 "지금 고치려는 버튼이 코드의 어디에 있는지" 찾는 데만 한참 걸리게 된다. 컴포넌트를 기능 단위로 나눠두면 필요한 부분만 빠르게 찾아 수정할 수 있다.


3. props로 데이터 전달하기

지금까지 만든 Welcome은 항상 같은 문구만 보여준다. 컴포넌트마다 다른 값을 보여주고 싶을 때는 props를 사용한다. props는 부모 컴포넌트가 자식 컴포넌트에게 전달하는 데이터다.

function Profile(props) {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>{props.bio}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <Profile name="한코딩" bio="프론트엔드 개발자" />
      <Profile name="김리액트" bio="주니어 개발자" />
    </div>
  );
}

export default App;

<Profile name="한코딩" bio="프론트엔드 개발자" />처럼 태그에 속성을 적으면, Profile 함수는 { name: "한코딩", bio: "프론트엔드 개발자" }라는 객체를 props라는 이름으로 통째로 받는다.


4. props 구조 분해 할당

props.name, props.bio처럼 매번 props.을 붙이는 대신, 자바스크립트의 구조 분해 할당 문법을 사용하면 코드를 더 간결하게 쓸 수 있다.

function Profile({ name, bio }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{bio}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <Profile name="한코딩" bio="프론트엔드 개발자" />
      <Profile name="김리액트" bio="주니어 개발자" />
    </div>
  );
}

export default App;

왜 굳이 구조 분해 할당을 쓸까? props.name, props.bio처럼 매번 props.을 반복해서 적으면 코드가 길어지고, 이 컴포넌트가 어떤 props를 받는지도 함수 내용을 다 읽어야 알 수 있다. { name, bio }처럼 함수의 매개변수 자리에서 바로 구조 분해를 하면, 함수 이름만 봐도 "아, 이 컴포넌트는 name과 bio를 받는구나"를 한눈에 알 수 있다.


5. children props

컴포넌트 태그 사이에 무언가를 넣으면, 그 내용은 자동으로 props.children이라는 이름으로 전달된다.

function Card({ children }) {
  return <div style={{ border: "1px solid gray", padding: "12px" }}>{children}</div>;
}

function App() {
  return (
    <Card>
      <h2>공지사항</h2>
      <p>이번 주 금요일은 휴강입니다.</p>
    </Card>
  );
}

export default App;

<Card>와 </Card> 사이에 적은 <h2>, <p> 태그들이 통째로 Card 컴포넌트의 children으로 전달되어, 테두리가 있는 박스 안에 그대로 렌더링된다.


6. 실습 정리

실습 1) Profile 컴포넌트 만들기

  1. src/App.jsx의 내용을 모두 지운다.
  2. name, job, bio 세 가지 props를 받는 Profile 컴포넌트를 구조 분해 할당 방식으로 작성한다.
  3. App 컴포넌트 안에서 <Profile />을 사용해 자신의 이름, 직업(또는 꿈), 소개를 props로 전달해본다.

실습 2) 여러 번 재사용해보기

  1. 같은 Profile 컴포넌트를 서로 다른 props 값으로 2~3번 더 사용해본다.
  2. 컴포넌트를 하나만 만들어뒀는데도 여러 개의 서로 다른 프로필 카드가 만들어지는 것을 확인한다.

실습 3) children으로 감싸는 컴포넌트 만들어보기

  1. children을 받아서 테두리와 배경색이 있는 박스로 감싸주는 Box 컴포넌트를 만든다.
  2. <Box> 태그 사이에 방금 만든 <Profile /> 컴포넌트를 넣어서, 프로필 카드가 박스 안에 예쁘게 감싸지는지 확인한다.

마무리

  • 컴포넌트는 "화면 조각을 만드는 함수"이고, 이름은 반드시 대문자로 시작해야 JSX가 HTML 태그와 구분할 수 있다.
  • props는 부모가 자식에게 전달하는 데이터이며, { name, bio }처럼 구조 분해 할당으로 받으면 코드가 훨씬 간결해진다.
  • 태그 사이에 넣은 내용은 자동으로 children으로 전달되며, 이는 박스·카드처럼 내용물을 감싸는 컴포넌트를 만들 때 유용하다.

⚛️ React 미니 프로젝트 - 프로필 카드 목록

지금까지 배운 React 입문 개념을 총정리하고, 이를 활용해 사진·직업 정보가 담긴 프로필 카드 3개를 나란히 보여주는 미니 프로젝트를 만들어봤다.

1. 리액트 시작 섹션 내용 정리

지금까지 배운 내용을 순서대로 이으면 이런 흐름이었다.

노트핵심 개념한 줄 요약
웹 개발의 흐름과 React 등장 배경Virtual DOM, 선언적 UI화면을 직접 조작하지 않고, "이렇게 보여야 한다"만 선언하면 React가 알아서 반영해준다
개발 환경 구축개발 환경Vite로 프로젝트를 만들고 npm run dev로 실행한다
JSX(JavaScript XML) 문법JSX{ }로 자바스크립트 값을 화면에 끼워 넣는, HTML을 닮은 자바스크립트 문법이다
컴포넌트(Component)와 Props컴포넌트, props화면을 재사용 가능한 조각(컴포넌트)으로 나누고, props로 서로 다른 데이터를 전달한다

앞에서 만든 Profile 컴포넌트는 이 네 가지 개념이 전부 합쳐진 결과물이었다. JSX로 구조를 짜고, props로 사람마다 다른 값을 받아서, 하나의 컴포넌트를 여러 번 재사용한 것이다.


2. 오늘의 미니 프로젝트: 프로필 카드 목록

지금까지 만든 Profile 컴포넌트를 확장해서, 사진과 직업 정보까지 포함한 프로필 카드 3개를 나란히 보여주는 목록을 완성한다.

카드 3개를 만드는데 컴포넌트는 왜 하나만 있으면 될까? Profile 컴포넌트 없이 만들었다면 카드 3개 분량의 HTML 구조(이미지, 이름, 직업, 소개, 버튼)를 세 번 반복해서 작성해야 했을 것이다. 컴포넌트 하나를 만들어두고 props만 다르게 넘기면, 코드는 한 번만 작성하고 데이터만 바꿔서 여러 번 재사용할 수 있다.


3. 실습 단계별 정리

실습 1) Profile 컴포넌트에 props 추가하기

앞에서 만든 Profile은 name, bio만 받았다. 여기에 job(직업)과 photo(사진 URL)를 추가한다.

function Profile({ name, job, photo, bio }) {
  return (
    <div>
      <img src={photo} alt={name} style={{ width: "80px", height: "80px", borderRadius: "50%" }} />
      <h3>{name}</h3>
      <p>{job}</p>
      <p>{bio}</p>
      <button>프로필 보기</button>
    </div>
  );
}

실습 2) 서로 다른 프로필 3개 준비하기

App 컴포넌트 안에서 Profile을 3번 사용하되, 이름·직업·소개·사진을 각각 다르게 전달한다. 사진은 아직 실제 이미지가 없어도 되니, 임시 이미지 서비스 주소를 사용해도 된다.

function App() {
  return (
    <div>
      <Profile
        name="한코딩"
        job="프론트엔드 개발자"
        photo="https://placehold.co/80x80"
        bio="React를 가르치고 있어요."
      />
      <Profile
        name="김리액트"
        job="주니어 개발자"
        photo="https://placehold.co/80x80"
        bio="이제 막 React를 배우기 시작했어요."
      />
      <Profile
        name="이자바"
        job="백엔드 개발자"
        photo="https://placehold.co/80x80"
        bio="프론트엔드에도 관심이 많아요."
      />
    </div>
  );
}

export default App;

실습 3) 카드를 가로로 나란히 정렬하기

지금은 카드 3개가 세로로 길게 쌓여 있다. App을 감싸는 <div>에 style을 추가해서 가로로 나란히 정렬한다.

function App() {
  return (
    <div style={{ display: "flex", gap: "16px" }}>
      <Profile
        name="한코딩"
        job="프론트엔드 개발자"
        photo="https://placehold.co/80x80"
        bio="React를 가르치고 있어요."
      />
      <Profile
        name="김리액트"
        job="주니어 개발자"
        photo="https://placehold.co/80x80"
        bio="이제 막 React를 배우기 시작했어요."
      />
      <Profile
        name="이자바"
        job="백엔드 개발자"
        photo="https://placehold.co/80x80"
        bio="프론트엔드에도 관심이 많아요."
      />
    </div>
  );
}

export default App;

display: "flex"는 안의 요소들을 가로 방향으로 나란히 배치해주고, gap: "16px"는 카드 사이 간격을 벌려준다.

실습 4) [선택] 카드에 테두리 스타일 입히기

Profile 컴포넌트의 최상위 <div>에도 style을 추가해서, 카드처럼 테두리와 여백이 있는 모양으로 꾸며본다.

function Profile({ name, job, photo, bio }) {
  return (
    <div style={{ border: "1px solid #ddd", borderRadius: "8px", padding: "16px", width: "180px" }}>
      <img src={photo} alt={name} style={{ width: "80px", height: "80px", borderRadius: "50%" }} />
      <h3>{name}</h3>
      <p>{job}</p>
      <p>{bio}</p>
      <button>프로필 보기</button>
    </div>
  );
}

마무리

  • 이번 미니 프로젝트는 Virtual DOM/선언적 UI(개념) → Vite(환경) → JSX(문법) → 컴포넌트/props(구조화)까지, 입문 섹션에서 배운 내용을 하나로 엮어보는 실습이었다.
  • 핵심은 "컴포넌트 하나 + 다른 props"로 여러 개의 서로 다른 결과물을 만들어낸다는 점이다. 카드가 10개, 100개로 늘어나도 Profile 컴포넌트 자체는 그대로 두고 데이터만 추가하면 된다.
  • display: "flex" + gap은 카드형 UI를 가로로 나란히 배치할 때 자주 쓰이는 조합이니 기억해두면 좋다.

⚛️ React useState 정리

React의 상태 관리 기본인 useState를 정리했다. 왜 일반 변수로는 화면이 안 바뀌는지부터, useState 문법, 리렌더링 원리, 여러 state를 동시에 다루는 방법까지 다룬다.

1. state란 무엇인가

React에서는 일반 변수의 값을 바꿔도 화면이 자동으로 다시 그려지지 않는다. 아래 코드로 직접 확인해보자.

let count = 0;

function App() {
  const handleClick = () => {
    count = count + 1;
    console.log(count);
  };

  return (
    <div>
      <p>현재 값: {count}</p>
      <button onClick={handleClick}>+1</button>
    </div>
  );
}

export default App;

버튼을 눌러보면, 크롬 개발자 도구의 Console 탭에는 1, 2, 3 ... 값이 잘 찍히는데, 정작 화면의 "현재 값: 0"은 그대로다. count 변수의 값은 분명히 바뀌었는데, React는 그 변화를 모르기 때문에 화면을 다시 그려주지 않는 것이다.

이 문제를 해결하는 것이 바로 state다. state는 "값이 바뀌면 화면도 자동으로 다시 그려달라"고 React에게 등록해두는 특별한 변수다.


2. useState 훅 문법

state를 만들 때는 React가 제공하는 useState라는 함수를 사용한다. 위 코드를 이렇게 바꿔보자.

import { useState } from "react";

function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>현재 값: {count}</p>
      <button onClick={handleClick}>+1</button>
    </div>
  );
}

export default App;

이제 버튼을 클릭하면 화면의 숫자가 실제로 올라가는 것을 확인할 수 있다.

코드의미
useState(0)state의 초기값을 0으로 설정
count현재 state 값 (읽기 전용)
setCountstate 값을 바꿀 때 사용하는 함수

useState(0)을 호출하면 [현재값, 값을바꾸는함수] 형태의 배열이 돌아오는데, 이걸 const [count, setCount] = ...처럼 구조 분해 할당으로 한 번에 꺼내서 사용한다.


3. state 업데이트 시 리렌더링 원리

setCount를 호출하면 React는 "이 컴포넌트, 다시 그려야겠다"고 인식하고 App 함수를 처음부터 다시 실행한다. 이때 useState(0)은 이제 처음 값(0)이 아니라 가장 최근에 저장된 값을 돌려준다. 그래서 화면에 최신 값이 반영되는 것이다.

count = count + 1처럼 변수를 직접 바꾸면 왜 안 될까? React는 setCount처럼 정해진 함수를 통해서만 "값이 바뀌었다"는 사실을 알아챌 수 있다. count = count + 1처럼 변수를 직접 바꾸는 방식은 앞서 확인했듯 React가 전혀 눈치채지 못한다. 그래서 state 값을 바꿀 때는 반드시 useState가 만들어준 함수(setCount)를 사용해야 한다.


4. 여러 개의 state 다루기

컴포넌트 하나에 useState를 여러 번 사용해서, 서로 다른 값을 각각의 state로 관리할 수 있다.

import { useState } from "react";

function App() {
  const [name, setName] = useState("한코딩");
  const [likeCount, setLikeCount] = useState(0);

  return (
    <div>
      <p>이름: {name}</p>
      <p>좋아요: {likeCount}</p>
      <button onClick={() => setLikeCount(likeCount + 1)}>좋아요</button>
    </div>
  );
}

export default App;

name과 likeCount는 서로 완전히 독립된 state다. likeCount가 바뀌어도 name은 그대로 유지된다.


5. 실습 정리

실습 1) 일반 변수로 카운터를 만들어보고 문제 확인하기

  1. src/App.jsx에 "일반 변수" 카운터 코드를 붙여넣어 실행한다.
  2. 버튼을 여러 번 눌러보고, 콘솔 값은 올라가지만 화면 숫자는 그대로인 것을 직접 확인한다.

실습 2) useState로 카운터 만들기

  1. useState를 사용해 진짜로 동작하는 클릭 카운터를 만든다.
  2. 버튼을 클릭할 때마다 화면의 숫자가 실제로 올라가는지 확인한다.

실습 3) 증가·감소·리셋 버튼 만들기

  1. +1 버튼 옆에 -1 버튼과 리셋 버튼을 추가한다.
  2. -1 버튼은 setCount(count - 1)을, 리셋 버튼은 setCount(0)을 호출하도록 만든다.

실습 4) 여러 state 함께 다루기

  1. useState를 두 번 사용해서, 좋아요 수(숫자)와 좋아요 여부(true/false) 두 가지 state를 각각 만든다.
  2. 버튼을 클릭하면 좋아요 수는 1씩 늘어나고, 동시에 좋아요 여부도 true로 바뀌도록 만들어본다.

마무리

  • 일반 변수는 값이 바뀌어도 React가 알지 못해서 화면이 갱신되지 않는다. state는 "값이 바뀌면 화면도 다시 그려달라"고 React에 등록하는 장치다.
  • const [값, set함수] = useState(초기값) 패턴을 기본 틀로 외워두면 어떤 state를 만들든 헷갈리지 않는다.
  • state는 반드시 set함수로만 변경해야 하며, 변수 = 새값처럼 직접 대입하면 React가 변화를 감지하지 못한다.
  • 서로 다른 데이터는 useState를 여러 번 호출해서 각각 독립된 state로 관리하는 게 기본 패턴이다.

⚛️ React 이벤트 처리 정리

React에서 클릭, 입력 같은 사용자 동작을 다루는 방법을 정리했다. 주요 이벤트 종류부터 이벤트 핸들러 작성 패턴, 이벤트 객체(e.target.value)까지 다룬다.

1. 주요 이벤트 종류

사용자가 버튼을 클릭하거나, 입력창에 글자를 타이핑하거나, 폼을 제출하는 것처럼 화면에서 일어나는 동작을 이벤트(event)라고 부른다. React에서는 HTML 태그에 이벤트별로 정해진 속성을 붙여서 처리한다.

이벤트 속성언제 발생하는가
onClick요소를 클릭했을 때
onChangeinput, textarea 등의 값이 바뀔 때
onSubmitform이 제출될 때
onMouseEnter마우스가 요소 위에 올라갔을 때

2. 이벤트 핸들러 함수 작성 패턴

이벤트가 발생했을 때 실행할 함수를 이벤트 핸들러라고 부른다. 작성 방식은 크게 두 가지다.

function App() {
  // 방식 1: 분리된 함수로 작성
  const handleClick = () => {
    alert("버튼을 클릭했습니다!");
  };

  return (
    <div>
      <button onClick={handleClick}>분리된 함수</button>

      {/* 방식 2: 인라인(그 자리에서 바로) 작성 */}
      <button onClick={() => alert("인라인으로 작성했습니다!")}>인라인 함수</button>
    </div>
  );
}

export default App;

onClick={handleClick}이라고 써야지 왜 onClick={handleClick()}이라고 쓰면 안 될까? onClick={handleClick()}처럼 괄호를 붙이면 "클릭했을 때 실행해줘"가 아니라 "지금 당장 실행해줘"라는 뜻이 된다. 그러면 버튼을 누르기도 전에 화면이 그려지는 순간 함수가 바로 실행되어버린다. 이벤트 핸들러는 함수를 "지금 호출"하는 게 아니라 "나중에 클릭됐을 때 실행할 함수 자체"를 전달해야 하기 때문에 괄호 없이 함수 이름만 적어야 한다.


3. 이벤트 객체(event)와 e.target.value

이벤트 핸들러 함수는 React가 자동으로 이벤트 객체를 첫 번째 매개변수로 넘겨준다. 이 객체 안에는 "무슨 일이 일어났는지"에 대한 정보가 담겨 있다. 특히 입력창의 경우, e.target.value로 사용자가 입력한 값을 읽을 수 있다.

import { useState } from "react";

function App() {
  const [text, setText] = useState("");

  const handleChange = (e) => {
    setText(e.target.value);
  };

  return (
    <div>
      <input onChange={handleChange} placeholder="아무거나 입력해보세요" />
      <p>입력한 값: {text}</p>
    </div>
  );
}

export default App;

input에 글자를 입력할 때마다 onChange가 실행되고, e.target은 "이벤트가 일어난 그 input 태그 자신"을 가리킨다. 그래서 e.target.value는 지금 그 input 안에 있는 값을 의미한다.


4. 인라인 핸들러 vs 분리된 함수, 언제 뭘 쓸까

상황추천 방식
로직이 한 줄로 아주 간단할 때 (예: setCount(count + 1))인라인 방식
로직이 여러 줄이거나, 조건문이 들어갈 때분리된 함수 방식
같은 로직을 여러 버튼에서 재사용할 때분리된 함수 방식

5. 실습 정리

실습 1) 버튼 클릭 이벤트 다뤄보기

  1. 버튼을 클릭하면 alert()로 원하는 문구가 뜨는 코드를 분리된 함수 방식으로 작성한다.
  2. 같은 동작을 이번에는 인라인 방식으로도 작성해서, 두 방식이 똑같이 동작하는지 비교해본다.

실습 2) input 값 실시간으로 표시하기

  1. useState와 onChange를 사용해서, input에 입력한 글자가 바로 아래 문장에 그대로 표시되도록 만든다.
  2. 입력한 글자 수(text.length)도 함께 표시해본다.

실습 3) 두 개의 input 동시에 다루기

  1. 이름을 입력받는 input과 나이를 입력받는 input을 각각 만들고, 각각 다른 state로 관리한다.
  2. 두 값을 합쳐서 "OOO님은 OO살입니다"처럼 화면에 함께 출력해본다.

마무리

  • 이벤트 핸들러는 onClick={handler}처럼 함수 자체를 전달해야 하며, handler()처럼 괄호를 붙이면 즉시 실행되어버린다는 점을 꼭 기억하자.
  • onChange + e.target.value + useState 조합은 input 값을 실시간으로 화면에 반영하는 React의 가장 기본적인 패턴이다.
  • 로직이 짧으면 인라인, 길거나 재사용이 필요하면 분리된 함수로 — 상황에 맞게 선택하면 코드 가독성이 좋아진다.

0개의 댓글