[아이티센 부트캠프] React 응용예제

이언덕·2026년 5월 19일

아이티센 부트캠프

목록 보기
101/115
post-thumbnail

React 응용예제 실행 흐름

React 응용예제는 앞에서 배운 개념이 실제 코드에서 어떻게 사용되는지 확인하는 구간이다.
이번 예제는 EduApp1.jsx, EduApp2.jsx, EduApp3.jsx, EduApp4.jsx, main.jsx를 기준으로 진행한다.


각 예제는 필요한 개념을 짧게 복습한 뒤, 실제 예제 코드 안의 주석으로 값이 어디에서 만들어지고 어디로 전달되는지 확인한다.
그 다음 브라우저 결과물로 동작을 검증하고, 마지막에는 같은 개념을 조금 바꿔서 적용하는 추가 응용 예시까지 정리한다.


응용예제의 핵심은 코드를 외우는 것이 아니라, 값이 코드 안에서 어떻게 이동하고 화면에 어떻게 출력되는지 이해하는 것이다.



1. main.jsx에서 예제를 바꿔 실행하는 방식

1-1. main.jsx는 React 앱의 시작점이다

main.jsx는 React 앱을 브라우저 화면에 연결하는 시작 파일이다.
index.html에 있는 root 요소를 찾고, 그 안에 원하는 컴포넌트를 렌더링한다.


여기서 렌더링은 컴포넌트가 반환한 JSX를 화면에 보이도록 그리는 작업이다.
즉, 어떤 예제를 화면에서 확인할지는 main.jsx에서 어떤 컴포넌트를 렌더링하느냐에 따라 달라진다.


1-2. import와 렌더링은 서로 다른 단계이다

예제를 확인할 때는 main.jsx에서 필요한 컴포넌트를 import한다.
import는 다른 파일에서 내보낸 컴포넌트를 현재 파일로 가져오는 문법이다.


하지만 import만 했다고 화면에 바로 보이는 것은 아니다.
가져온 컴포넌트를 render() 안에서 <컴포넌트 /> 형태로 사용해야 브라우저 화면에 표시된다.


예를 들어 EduApp1.jsx를 확인할 때는 EduApp1을 가져오고, 렌더링 위치에 <EduApp1 />을 넣는다.

// main.jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import EduApp1 from './EduApp1.jsx';
import './index.css';

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <EduApp1 />
  </StrictMode>,
);

이 코드에서는 EduApp1만 화면에 렌더링된다.
다른 예제를 확인하고 싶으면 import할 컴포넌트와 <EduApp1 /> 자리에 들어갈 컴포넌트를 바꾸면 된다.


1-3. default export와 named export에 따라 import 방식이 다르다

컴포넌트를 가져오는 방식은 파일에서 어떻게 내보냈는지에 따라 달라진다.
EduApp1.jsx처럼 export default로 내보낸 컴포넌트는 중괄호 없이 가져온다.

// main.jsx
import EduApp1 from './EduApp1.jsx';

default export는 파일에서 기본으로 내보내는 값이다.
그래서 가져올 때 중괄호 없이 가져온다.


반면 EduApp3.jsx의 PostList처럼 export function PostList() 형태로 이름을 붙여 내보낸 컴포넌트는 중괄호로 가져온다.

// main.jsx
import { PostList } from './EduApp3.jsx';

이 방식은 named export이다.
named export는 내보낸 이름과 같은 이름으로 가져와야 한다.


정리하면 화면에 표시되려면 컴포넌트를 import하는 것에서 끝나지 않고, render() 안에서 실제로 렌더링해야 한다.


1-4. 예제별 main.jsx 교체 흐름

예제는 한 번에 여러 개를 다 켜는 방식보다, 하나씩 바꿔가며 확인하는 방식이 이해하기 쉽다.
그래야 코드 하나와 결과물 하나를 바로 연결해서 볼 수 있다.


예를 들어 EduApp3.jsx의 PostList를 확인하려면 main.jsx를 다음처럼 바꾼다.

// main.jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { PostList } from './EduApp3.jsx';
import './index.css';

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <PostList />
  </StrictMode>,
);

이렇게 하면 브라우저에는 PostList 컴포넌트의 결과만 출력된다.


StateTest를 확인하고 싶다면 EduApp4.jsx에서 StateTest를 가져와서 렌더링하면 된다.

// main.jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { StateTest } from './EduApp4.jsx';
import './index.css';

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <StateTest />
  </StrictMode>,
);

이렇게 예제마다 main.jsx에서 가져오는 컴포넌트와 렌더링하는 컴포넌트를 바꿔 확인한다.


정리하면 main.jsx는 예제를 실행하는 연결 지점이다.
실행할 예제를 바꾸려면 import할 컴포넌트와 렌더링할 컴포넌트를 함께 바꿔야 한다.




EduApp1.jsx: props 기본 예제

EduApp1.jsx는 부모 컴포넌트가 자식 컴포넌트에게 값을 전달하는 props 기본 흐름을 확인하는 예제이다.
props는 컴포넌트 사이에서 데이터를 전달할 때 사용하는 값이다.


이 예제에서는 EduApp1이 Welcome 컴포넌트에게 문자열 "React"를 name이라는 이름으로 전달한다.
Welcome은 전달받은 name 값을 JSX 중괄호 위치에 넣어 화면에 출력한다.



2. props 개념 복습

2-1. props는 부모가 자식에게 전달하는 값이다

props는 부모 컴포넌트가 자식 컴포넌트에게 전달하는 값이다.
컴포넌트 태그에 속성처럼 작성해서 값을 넘긴다.


예를 들어 <Welcome name="React" />에서 name="React"가 props이다.
여기서 "React"라는 문자열 값이 name이라는 이름으로 Welcome 컴포넌트에 전달된다.


2-2. 구조 분해 할당으로 props 값을 꺼낼 수 있다

자식 컴포넌트는 전달받은 props를 객체 형태로 받을 수 있다.
하지만 props.name처럼 매번 객체 이름을 붙여 쓰면 코드가 길어진다.


그래서 필요한 값만 바로 꺼내기 위해 구조 분해 할당을 사용한다.
구조 분해 할당은 객체 안의 값을 변수처럼 바로 꺼내는 문법이다.


예를 들어 function Welcome({ name })은 전달받은 props 객체 안에서 name 값만 꺼내겠다는 뜻이다.


2-3. JSX 중괄호로 전달받은 값을 화면에 출력한다

JSX 안에서 JavaScript 값을 출력하려면 중괄호 {}를 사용한다.
그래서 전달받은 name 값을 화면에 출력하려면 {name}처럼 작성한다.


props로 받은 값은 JSX 중괄호 안에서 화면에 출력할 수 있다.



3. EduApp1.jsx 코드 흐름

3-1. 전체 코드

// EduApp1.jsx
function Welcome({ name }) {
  // EduApp1에서 전달한 name props의 값 "React"가 name 변수에 들어온다.
  // 그래서 아래 {name} 위치에는 문자열 "React"가 들어간다.
  return <h1>안녕? {name}</h1>;
}

export default function EduApp1() {
  // 문자열 "React"를 Welcome 컴포넌트의 name props로 전달한다.
  // 이 코드 때문에 Welcome 내부의 name 값은 "React"가 된다.
  return <Welcome name="React" />;
}

이 파일에는 Welcome 컴포넌트와 EduApp1 컴포넌트가 있다.
EduApp1은 Welcome을 사용하면서 name이라는 이름으로 문자열 "React"를 전달한다.


Welcome은 name 값을 받아 <h1> 안의 {name} 위치에 넣는다.
그래서 최종적으로 화면에는 안녕? React가 출력된다.


3-2. 실행 흐름

코드 실행 흐름은 다음과 같다.

  • main.jsx에서 <EduApp1 />을 렌더링한다.
  • EduApp1 컴포넌트가 실행된다.
  • EduApp1은 <Welcome name="React" />를 반환한다.
  • 문자열 "React"가 Welcome의 name props로 전달된다.
  • Welcome의 매개변수 { name } 안에 "React"가 들어온다.
  • Welcome 내부의 {name} 위치에 "React"가 들어간다.
  • 브라우저에는 안녕? React가 출력된다.

이 흐름에서 중요한 점은 Welcome이 직접 "React"라는 값을 만든 것이 아니라는 점이다.
"React"라는 값은 EduApp1이 전달했고, Welcome은 그 값을 받아서 화면에 출력했다.



4. EduApp1.jsx 결과물 확인

4-1. 브라우저 출력 결과

main.jsx에서 EduApp1을 렌더링하면 브라우저에는 다음 결과가 출력된다.

부모 컴포넌트가 전달한 name="React" 값이 자식 컴포넌트에서 {name}으로 출력된다.


결과 화면의 안녕? React는 Welcome 컴포넌트가 만든 문장이다.
하지만 문장 안의 React 값은 EduApp1 컴포넌트가 전달한 props이다.


정리하면 EduApp1.jsx는 부모에서 자식으로 props를 전달하고, 자식이 그 값을 화면에 출력하는 기본 흐름을 보여준다.



5. EduApp1.jsx 추가 응용 예시

5-1. name 외에 message props도 함께 전달하기

이번에는 name뿐 아니라 message 값도 함께 전달해 본다.
하나의 컴포넌트에는 props를 여러 개 전달할 수 있다.

// EduApp1ApplyExample.jsx
function Welcome({ name, message }) {
  // name에는 "React"가 들어오고, message에는 "props를 복습하는 중이다."가 들어온다.
  return (
    <section>
      <h1>안녕? {name}</h1>
      <p>{message}</p>
    </section>
  );
}

export default function EduApp1ApplyExample() {
  // name props에는 "React"를 전달한다.
  // message props에는 "props를 복습하는 중이다."를 전달한다.
  return <Welcome name="React" message="props를 복습하는 중이다." />;
}
// 출력결과
// 안녕? React
// props를 복습하는 중이다.

이 예시에서는 name과 message를 함께 전달한다.
자식 컴포넌트는 { name, message }로 두 값을 꺼낸 뒤 화면에 출력한다.


이렇게 하면 같은 Welcome 컴포넌트를 더 다양한 문구를 출력하는 컴포넌트로 사용할 수 있다.




EduApp2.jsx: div와 Fragment로 JSX 감싸기

EduApp2.jsx는 JSX에서 여러 요소를 반환할 때 하나의 부모 요소로 감싸야 한다는 규칙을 확인하는 예제이다.
React 컴포넌트는 한 번에 하나의 최상위 구조를 반환해야 한다.


여러 태그를 나란히 반환하려면 div 또는 Fragment로 묶어야 한다.
이 예제에서는 div로 묶는 방식과 Fragment로 묶는 방식을 비교한다.



6. JSX 부모 요소 규칙 복습

6-1. JSX는 최상위 요소 하나를 반환해야 한다

JSX에서 여러 요소를 반환할 때는 반드시 하나의 부모 요소로 감싸야 한다.
예를 들어 <h1>과 <p>를 나란히 반환하면 최상위 요소가 두 개가 되므로 오류가 난다.


그래서 여러 요소를 반환할 때는 하나의 부모 요소로 묶어야 한다.


6-2. div는 실제 DOM 태그를 만든다

div로 감싸면 문법 오류를 막을 수 있다.
하지만 실제 브라우저의 DOM 구조에도 <div> 태그가 추가된다.


DOM은 브라우저가 HTML 문서를 객체 구조로 해석한 것이다.
즉, div를 쓰면 화면 구조 안에 실제 박스가 하나 생긴다고 보면 된다.


6-3. Fragment는 실제 태그를 만들지 않고 묶기만 한다

Fragment는 여러 요소를 묶어 주지만 실제 HTML 태그를 추가하지 않는다.
짧은 문법으로는 <>...</>를 사용한다.


불필요한 div를 만들고 싶지 않을 때는 Fragment를 사용하는 것이 좋다.


div는 실제 태그를 만들고, Fragment는 실제 태그 없이 여러 요소를 묶는다.



7. EduApp2.jsx 코드 흐름

7-1. 전체 코드

// EduApp2.jsx
export function GoodExample1() {
  // <h1>제목1</h1>과 <p>본문1</p>를 실제 div 태그 안에 넣어 반환한다.
  // 이 div는 JSX를 하나로 묶는 역할도 하고, 실제 DOM에도 남는다.
  return (
    <div>
      <h1>제목1</h1>
      <p>본문1</p>
    </div>
  );
}

export function GoodExample2() {
  // <h1>제목2</h1>와 <p>본문2</p>를 Fragment로 묶어 반환한다.
  // Fragment는 JSX를 하나로 묶지만, 실제 DOM에는 별도 태그를 만들지 않는다.
  return (
    <>
      <h1>제목2</h1>
      <p>본문2</p>
    </>
  );
}

GoodExample1은 <div>로 <h1>과 <p>를 감싼다.
GoodExample2는 Fragment로 <h1>과 <p>를 감싼다.


두 방식 모두 JSX 문법상 하나의 최상위 구조를 만든다.
차이는 실제 HTML 태그가 추가되는지 여부이다.


7-2. 코드 흐름을 비교해서 보면

GoodExample1은 실제 DOM에 다음처럼 감싸는 <div>가 남는다.

// GoodExample1ResultShape.jsx
<div>
  <h1>제목1</h1>
  <p>본문1</p>
</div>

이 구조에서는 최상위 요소가 <div> 하나이다.
그래서 React가 정상적으로 렌더링할 수 있다.


반면 GoodExample2는 Fragment로 묶었기 때문에 실제 DOM에는 감싸는 태그가 남지 않는다.

// GoodExample2ResultShape.jsx
<h1>제목2</h1>
<p>본문2</p>

즉, Fragment는 문법적으로 여러 요소를 하나로 묶지만, 실제 화면 구조에는 불필요한 태그를 추가하지 않는다.



8. EduApp2.jsx 결과물 확인

8-1. 브라우저 출력 결과

GoodExample1과 GoodExample2를 함께 렌더링하면 브라우저에는 다음 결과가 출력된다.

div와 Fragment는 모두 여러 요소를 묶을 수 있지만, div는 실제 태그를 만들고 Fragment는 실제 태그를 추가하지 않는다.


결과 화면에서는 제목1, 본문1, 제목2, 본문2가 모두 정상 출력된다.
겉으로 보이는 결과는 비슷할 수 있지만 내부 구조는 다르다.


정리하면 화면 구조상 실제 박스가 필요하면 div, 단순히 묶기만 하면 되면 Fragment를 사용한다.



9. EduApp2.jsx 추가 응용 예시

9-1. Fragment로 카드 내용을 묶기

이번에는 카드 하나를 반환한다고 생각해 보자.
카드 전체를 감싸는 div가 이미 필요하지 않은 상황이라면 Fragment로 여러 요소만 묶을 수 있다.

// EduApp2ApplyExample.jsx
export default function EduApp2ApplyExample() {
  // <h2>, <p>, <button>을 Fragment로 묶어 반환한다.
  // 실제 DOM에는 감싸는 div 없이 세 요소만 출력된다.
  return (
    <>
      <h2>React 학습 카드</h2>
      <p>컴포넌트와 props를 복습한다.</p>
      <button>자세히 보기</button>
    </>
  );
}
// 출력결과
// React 학습 카드
// 컴포넌트와 props를 복습한다.
// 자세히 보기

이 예시에서는 <h2>, <p>, <button>을 Fragment로 묶었다.
불필요한 감싸는 태그를 추가하지 않으면서 여러 요소를 함께 반환할 수 있다.


다만 스타일을 적용할 박스가 필요하다면 Fragment보다 div가 더 적절하다.




EduApp3.jsx: JSX 핵심 문법 종합 예제

EduApp3.jsx는 JSX 표현식, 이벤트 처리, 인라인 스타일, 조건부 렌더링, 리스트 렌더링, children props를 나누어 확인하는 종합 예제이다.
각 함수는 하나의 개념을 확인하기 위한 작은 예제 컴포넌트이다.


이 파일은 React의 핵심 문법이 실제 코드에서 어떻게 보이는지 빠르게 확인하기 좋다.
하나씩 렌더링하면서 결과를 확인하면 JSX 문법이 화면과 어떻게 연결되는지 이해하기 쉽다.



10. UserGreeting: JSX 표현식과 삼항 연산자

10-1. 개념 복습

JSX 안에서 JavaScript 값을 출력하려면 중괄호 {}를 사용한다.
변수 값, 계산 결과, 삼항 연산자처럼 결과값이 나오는 표현식을 중괄호 안에 넣을 수 있다.


삼항 연산자는 조건에 따라 둘 중 하나의 값을 선택하는 표현식이다.
형태는 조건 ? 참일 때 값 : 거짓일 때 값이다.


10-2. 코드 흐름

// EduApp3.jsx
export function UserGreeting() {
  const name = '도우너';
  // name 변수에는 문자열 "도우너"가 저장된다.

  const score = 90;
  // score 변수에는 숫자 90이 저장된다.

  return (
    <section>
      <h1>{name}님 환영한다.</h1>
      {/* name 값 "도우너"가 {name} 위치에 들어가 "도우너님 환영한다."가 된다. */}

      <p>점수: {score}</p>
      {/* score 값 90이 {score} 위치에 들어가 "점수: 90"이 된다. */}

      <p>합격 여부: {score >= 60 ? '합격' : '불합격'}</p>
      {/* score가 90이므로 score >= 60은 true이다. */}
      {/* true이면 "합격"이 선택되어 화면에 출력된다. */}
    </section>
  );
}

name에는 "도우너"가 저장된다.
score에는 90이 저장된다.


{name}은 name 값을 화면에 출력한다.
{score}는 score 값을 화면에 출력한다.
{score >= 60 ? '합격' : '불합격'}은 점수가 60 이상이면 합격, 아니면 불합격을 출력한다.


현재 score는 90이므로 score >= 60 조건이 참이다.
따라서 화면에는 합격이 출력된다.


10-3. 결과물 확인

JSX 중괄호 안에서는 변수와 삼항 연산자처럼 결과값을 만드는 표현식을 사용할 수 있다.


결과 화면에는 도우너님 환영한다., 점수: 90, 합격 여부: 합격이 출력된다.
이 결과는 name, score, 삼항 연산자 결과가 각각 화면에 들어갔다는 뜻이다.


정리하면 UserGreeting 예제는 JSX 중괄호 안에 변수와 표현식을 넣어 화면을 만드는 흐름을 보여준다.


10-4. 추가 응용 예시

이번에는 점수에 따라 등급을 출력해 본다.
조건이 두 가지보다 많아지면 삼항 연산자를 중첩해서 사용할 수 있다.

// UserGreetingGradeExample.jsx
export default function UserGreetingGradeExample() {
  const name = '도우너';
  const score = 90;

  const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : 'C';
  // score가 90 이상이면 grade에 "A"가 저장된다.
  // score가 80 이상이면 grade에 "B"가 저장된다.
  // 둘 다 아니면 grade에 "C"가 저장된다.

  return (
    <section>
      <h1>{name}님 성적표</h1>
      <p>점수: {score}</p>
      <p>등급: {grade}</p>
    </section>
  );
}
// 출력결과
// 도우너님 성적표
// 점수: 90
// 등급: A

이 예시는 단순히 합격 여부만 출력하지 않는다.
score 값에 따라 A, B, C 중 하나를 계산해서 화면에 출력한다.


다만 조건이 너무 많아지면 삼항 연산자가 복잡해질 수 있다.
그럴 때는 함수를 따로 만들어 등급을 계산하는 방식이 더 읽기 쉽다.



11. ClickButton: 이벤트 처리

11-1. 개념 복습

이벤트는 사용자가 화면에서 일으키는 동작이다.
예를 들어 버튼 클릭, 입력창 입력, 폼 제출 같은 동작이 이벤트이다.


React에서는 클릭 이벤트를 onClick으로 작성한다.
그리고 onClick에는 실행할 함수를 전달한다.


중요한 점은 onClick={handleClick}처럼 함수 이름을 전달해야 한다는 것이다.
onClick={handleClick()}처럼 괄호를 붙이면 렌더링되는 순간 함수가 바로 실행될 수 있다.


11-2. 코드 흐름

// EduApp3.jsx
export function ClickButton() {
  function handleClick() {
    // 버튼을 클릭했을 때만 이 함수가 실행된다.
    // 실행되면 "버튼을 클릭했습니다."라는 문자열이 alert()로 전달된다.
    alert('버튼을 클릭했습니다.');
  }

  // onClick에는 handleClick()의 실행 결과가 아니라 handleClick 함수 자체를 전달한다.
  // 그래서 화면이 처음 렌더링될 때는 alert가 실행되지 않는다.
  // 사용자가 버튼을 클릭하는 순간 handleClick 함수가 실행된다.
  return <button onClick={handleClick}>클릭</button>;
}

handleClick 함수는 알림창을 띄우는 함수이다.
button 태그의 onClick에 handleClick 함수를 전달했다.


그래서 버튼이 처음 렌더링될 때는 함수가 실행되지 않는다.
사용자가 버튼을 클릭했을 때 handleClick이 실행된다.


11-3. 결과물 확인

onClick={handleClick}은 렌더링 순간 실행하는 것이 아니라, 버튼을 클릭했을 때 실행할 함수를 연결하는 방식이다.


결과물에서는 클릭 버튼을 누른 뒤 버튼을 클릭했습니다. 알림창이 뜬다.
즉, 이벤트와 함수가 정상적으로 연결된 것이다.


정리하면 이벤트 속성에는 실행 결과가 아니라, 나중에 실행할 함수를 전달해야 한다.


11-4. 추가 응용 예시

이번에는 버튼마다 다른 메시지를 출력해 본다.
인자가 필요한 경우에는 화살표 함수로 감싸서 전달한다.

// ClickButtonMessageExample.jsx
export default function ClickButtonMessageExample() {
  function showMessage(message) {
    // 클릭한 버튼에 따라 전달받은 message 값이 달라진다.
    alert(message);
  }

  return (
    <section>
      <button onClick={() => showMessage('저장 버튼 클릭')}>저장</button>
      <button onClick={() => showMessage('삭제 버튼 클릭')}>삭제</button>
    </section>
  );
}
// 저장 버튼 클릭 결과
// 저장 버튼 클릭

// 삭제 버튼 클릭 결과
// 삭제 버튼 클릭

onClick={() => showMessage('저장 버튼 클릭')}은 클릭하기 전까지 showMessage()를 실행하지 않는다.
버튼을 클릭했을 때 화살표 함수가 실행되고, 그 안에서 showMessage()가 실행된다.


이 방식은 이벤트 함수에 값을 전달해야 할 때 자주 사용한다.



12. Notice: 인라인 스타일 객체

12-1. 개념 복습

JSX에서 style 속성은 문자열이 아니라 객체로 작성한다.
HTML에서는 style="color:red"처럼 문자열로 쓸 수 있지만, JSX에서는 style={{ color: 'red' }}처럼 객체를 전달한다.


스타일 정보가 많아지면 객체를 따로 만들고, 그 객체를 style 속성에 전달할 수 있다.
이렇게 하면 코드가 더 읽기 쉬워진다.


12-2. 코드 흐름

// EduApp3.jsx
export function Notice() {
  const boxStyle = {
    backgroundColor: 'magenta',
    // backgroundColor 값 "magenta"가 div의 배경색으로 적용된다.

    padding: '16px',
    // padding 값 "16px"가 div의 안쪽 여백으로 적용된다.

    borderRadius: '12px',
    // borderRadius 값 "12px"가 div의 모서리 둥글기로 적용된다.
  };

  // boxStyle 객체 전체가 style 속성으로 전달된다.
  // React는 boxStyle 안의 backgroundColor, padding, borderRadius를 div 스타일로 적용한다.
  return <div style={boxStyle}>공지사항이다.</div>;
}

boxStyle은 스타일 정보를 담은 객체이다.
backgroundColor는 배경색을 지정한다.
padding은 안쪽 여백을 지정한다.
borderRadius는 모서리를 둥글게 만든다.


<div style={boxStyle}>는 div에 boxStyle 객체를 스타일로 적용한다는 뜻이다.


12-3. 결과물 확인

JSX의 style 속성에는 스타일 정보를 담은 JavaScript 객체를 전달한다.


결과물에서는 공지사항이다. 문구에 magenta 배경, 여백, 둥근 모서리가 적용되어 있다.
즉, boxStyle 객체가 div에 정상 적용된 것이다.


정리하면 JSX 인라인 스타일은 문자열이 아니라 객체로 전달해야 한다.


12-4. 추가 응용 예시

이번에는 type 값에 따라 다른 스타일을 적용해 본다.
props로 받은 값에 따라 스타일 객체를 다르게 만들 수 있다.

// NoticeTypeExample.jsx
function NoticeBox({ type, message }) {
  const boxStyle = {
    backgroundColor: type === 'important' ? 'magenta' : 'gray',
    // type 값이 "important"이면 배경색으로 "magenta"를 사용한다.
    // type 값이 "important"가 아니면 배경색으로 "gray"를 사용한다.

    padding: '16px',
    borderRadius: '12px',
  };

  return <div style={boxStyle}>{message}</div>;
}

export default function NoticeTypeExample() {
  return (
    <section>
      <NoticeBox type="important" message="중요 공지사항이다." />
      <NoticeBox type="normal" message="일반 공지사항이다." />
    </section>
  );
}
// 출력결과
// 중요 공지사항이다.
// 일반 공지사항이다.

이 예시는 type 값에 따라 배경색을 다르게 적용한다.
type이 important이면 magenta, 그렇지 않으면 gray를 사용한다.


또한 message 값을 props로 전달해 공지 문구를 다르게 출력한다.
이렇게 하면 같은 컴포넌트를 상황에 따라 다른 스타일과 다른 문구로 사용할 수 있다.



13. LoginStatus: 삼항 연산자 조건부 렌더링

13-1. 개념 복습

조건부 렌더링은 조건에 따라 다른 화면을 보여주는 방식이다.
삼항 연산자는 둘 중 하나를 반드시 보여줘야 할 때 사용하기 좋다.


LoginStatus 예제에서는 isLogin 값이 true인지 false인지에 따라 다른 문구를 출력한다.


13-2. 코드 흐름

// EduApp3.jsx
export function LoginStatus({ isLogin }) {
  // isLogin props에 true가 들어오면 "로그인 상태"가 선택된다.
  // isLogin props에 false가 들어오면 "로그아웃 상태"가 선택된다.
  return <p>{isLogin ? '로그인 상태' : '로그아웃 상태'}</p>;
}

LoginStatus는 isLogin이라는 props를 받는다.
isLogin이 true이면 로그인 상태가 출력된다.
isLogin이 false이면 로그아웃 상태가 출력된다.


즉, 삼항 연산자가 조건에 따라 둘 중 하나의 문자열을 선택한다.


13-3. true 결과물 확인

isLogin 값이 true이면 삼항 연산자의 참 결과인 로그인 상태가 출력된다.


13-4. false 결과물 확인

isLogin 값이 false이면 삼항 연산자의 거짓 결과인 로그아웃 상태가 출력된다.


정리하면 삼항 연산자는 조건에 따라 둘 중 하나의 화면을 반드시 보여줘야 할 때 사용한다.


13-5. 추가 응용 예시

이번에는 로그인 상태에 따라 버튼 문구를 바꿔 본다.
문구뿐 아니라 버튼도 조건부 렌더링으로 바꿀 수 있다.

// LoginButtonStatusExample.jsx
function LoginButtonStatus({ isLogin }) {
  return (
    <button>
      {isLogin ? '로그아웃' : '로그인'}
      {/* isLogin이 true이면 버튼 안에 "로그아웃"이 들어간다. */}
      {/* isLogin이 false이면 버튼 안에 "로그인"이 들어간다. */}
    </button>
  );
}

export default function LoginButtonStatusExample() {
  return <LoginButtonStatus isLogin={false} />;
}
// 출력결과
// 로그인

isLogin이 false이므로 버튼에는 로그인이 출력된다.
만약 isLogin을 true로 바꾸면 버튼 문구는 로그아웃으로 바뀐다.


이처럼 조건부 렌더링은 안내 문구뿐 아니라 버튼, 메뉴, 입력 영역 등 다양한 화면에 적용할 수 있다.



14. AdminMenu: && 조건부 렌더링

14-1. 개념 복습

&& 연산자는 조건이 참일 때만 특정 화면을 보여주고 싶을 때 사용한다.
거짓일 때 따로 보여줄 내용이 없다면 삼항 연산자보다 &&가 더 간단하다.


AdminMenu 예제에서는 Home 링크는 항상 보이고, Admin 링크는 관리자일 때만 보인다.


14-2. 코드 흐름

// EduApp3.jsx
export function AdminMenu({ isAdmin }) {
  return (
    <nav>
      <a href="/">Home</a>
      {/* Home 링크는 조건 없이 항상 화면에 출력된다. */}

      <br />

      {isAdmin && <a href="/admin">Admin</a>}
      {/* isAdmin이 true이면 <a href="/admin">Admin</a>가 화면에 출력된다. */}
      {/* isAdmin이 false이면 Admin 링크는 화면에 출력되지 않는다. */}
    </nav>
  );
}

Home 링크는 조건 없이 항상 렌더링된다.
반면 Admin 링크는 {isAdmin && <a href="/admin">Admin</a>} 안에 있다.


isAdmin이 true이면 && 뒤의 Admin 링크가 렌더링된다.
isAdmin이 false이면 Admin 링크는 렌더링되지 않는다.


14-3. false 결과물 확인

isAdmin 값이 false이면 && 뒤의 Admin 링크는 렌더링되지 않는다.
그래서 화면에는 Home만 보인다.


14-4. true 결과물 확인

isAdmin 값이 true이면 && 뒤의 Admin 링크도 함께 렌더링된다.


정리하면 && 조건부 렌더링은 조건이 참일 때만 특정 요소를 보여주고 싶을 때 사용한다.


14-5. 추가 응용 예시

이번에는 관리자일 때만 관리자 안내 문구를 추가로 보여준다.
메뉴뿐 아니라 특정 영역 전체도 &&로 조건부 렌더링할 수 있다.

// AdminMessageExample.jsx
function AdminMessage({ isAdmin }) {
  return (
    <section>
      <h1>메인 화면</h1>

      {isAdmin && (
        <div>
          <h2>관리자 전용 영역</h2>
          <p>회원 관리와 게시글 관리를 사용할 수 있다.</p>
        </div>
      )}
      {/* isAdmin이 true이면 div 전체가 화면에 출력된다. */}
      {/* isAdmin이 false이면 div 전체가 출력되지 않는다. */}
    </section>
  );
}

export default function AdminMessageExample() {
  return <AdminMessage isAdmin={true} />;
}
// 출력결과
// 메인 화면
// 관리자 전용 영역
// 회원 관리와 게시글 관리를 사용할 수 있다.

isAdmin이 true이므로 관리자 전용 영역이 화면에 표시된다.
isAdmin이 false이면 <div> 전체가 렌더링되지 않는다.



15. PostList: map과 key로 리스트 렌더링

15-1. 개념 복습

리스트 렌더링은 배열 데이터를 반복해서 화면에 출력하는 방식이다.
React에서는 배열의 map() 메서드를 사용해 각 데이터를 JSX로 바꾼다.


반복해서 만들어지는 요소에는 key가 필요하다.
key는 각 항목을 구분하기 위해 React가 내부적으로 사용하는 값이다.


15-2. 코드 흐름

// EduApp3.jsx
export function PostList() {
  const posts = [
    { id: 1, title: 'React 기초' },
    { id: 2, title: 'JSX 문법' },
    { id: 3, title: '컴포넌트 분리' },
  ];

  return (
    <ul>
      {posts.map((post) => {
        return <li key={post.id}>{post.title}</li>;
      })}
    </ul>
  );
}

posts 배열에는 게시글 객체 3개가 들어 있다.
각 객체는 id와 title을 가진다.


먼저 posts 배열의 모양은 아래와 같다.

const posts = [
  { id: 1, title: 'React 기초' },
  { id: 2, title: 'JSX 문법' },
  { id: 3, title: '컴포넌트 분리' },
];

이 배열에서 첫 번째 객체는 아래 데이터이다.

{ id: 1, title: 'React 기초' }

두 번째 객체는 아래 데이터이다.

{ id: 2, title: 'JSX 문법' }

세 번째 객체는 아래 데이터이다.

{ id: 3, title: '컴포넌트 분리' }

이제 `map()`이 배열 안의 객체를 하나씩 꺼낸다. ```jsx posts.map((post) => { return
  • {post.title}
  • ; }); ``` 첫 번째 반복에서 `post`에는 아래 객체가 들어온다. ```jsx { id: 1, title: 'React 기초' } ``` 그러면 `post.id`는 `1`이고, `post.title`은 `'React 기초'`이다. ```jsx
  • React 기초
  • ``` 두 번째 반복에서 `post`에는 아래 객체가 들어온다. ```jsx { id: 2, title: 'JSX 문법' } ``` 그러면 `post.id`는 `2`이고, `post.title`은 `'JSX 문법'`이다. ```jsx
  • JSX 문법
  • ``` 세 번째 반복에서 `post`에는 아래 객체가 들어온다. ```jsx { id: 3, title: '컴포넌트 분리' } ``` 그러면 `post.id`는 `3`이고, `post.title`은 `'컴포넌트 분리'`이다. ```jsx
  • 컴포넌트 분리
  • ``` 결국 `posts` 배열은 아래와 같은 `li` 목록으로 바뀐다. 이때 `key`는 `React`가 내부에서 항목을 구분하기 위해 사용하는 값이다. 브라우저 화면에 직접 보이는 값은 아니다. ```jsx // React가 리스트 항목을 구분할 때 사용하는 JSX 구조
    • React 기초
    • JSX 문법
    • 컴포넌트 분리
    ``` 실제 화면에는 `key`가 보이지 않고, 아래 텍스트만 목록으로 출력된다. ```javascript // 출력결과 // React 기초 // JSX 문법 // 컴포넌트 분리 ``` `map()`은 배열의 데이터를 하나씩 꺼내서 화면에 보여줄 `JSX` 요소로 바꾸는 역할을 한다.

    ### 15-3. 결과물 확인

    map()은 배열 데이터를 반복하면서 각 항목을 JSX 화면 조각으로 바꾼다.


    결과물에는 React 기초, JSX 문법, 컴포넌트 분리가 목록으로 출력된다.
    이것은 posts 배열의 각 객체가 <li> 요소로 바뀐 결과이다.


    정리하면 리스트 렌더링은 배열 데이터를 map()으로 반복하면서 JSX로 변환하는 방식이다.


    15-4. 추가 응용 예시

    이번에는 게시글 제목뿐 아니라 작성자도 함께 출력해 본다.
    객체 배열에서는 필요한 속성을 여러 개 꺼내 화면에 사용할 수 있다.

    // PostListWriterExample.jsx
    export default function PostListWriterExample() {
      const posts = [
        { id: 1, title: 'React 기초', writer: '둘리' },
        { id: 2, title: 'JSX 문법', writer: '도우너' },
        { id: 3, title: '컴포넌트 분리', writer: '마이콜' },
      ];
      // 각 게시글 객체에는 id, title, writer 값이 함께 들어 있다.
    
      return (
        <ul>
          {posts.map((post) => (
            <li key={post.id}>
              {post.title} - 작성자: {post.writer}
            </li>
          ))}
        </ul>
      );
    }
    // 출력결과
    // React 기초 - 작성자: 둘리
    // JSX 문법 - 작성자: 도우너
    // 컴포넌트 분리 - 작성자: 마이콜

    이 예시는 post.title과 post.writer를 함께 출력한다.
    배열 안의 객체에서 여러 속성을 꺼내 화면을 구성하는 흐름이다.



    16. ChildrenTest: children props

    16-1. 개념 복습

    children은 컴포넌트 태그 사이에 작성한 내용을 전달받는 특별한 props이다.
    일반 props는 태그 속성으로 전달하지만, children은 시작 태그와 종료 태그 사이의 내용이 전달된다.


    예를 들어 <Panel>내용</Panel>에서 내용 부분이 children으로 전달된다.
    자식 컴포넌트는 {children} 위치에 그 내용을 출력한다.


    16-2. 코드 흐름

    // EduApp3.jsx
    function Panel({ children }) {
      return <section className="panel">{children}</section>;
    }
    
    export function ChildrenTest() {
      return (
        <Panel>
          <h2>공지사항</h2>
          <p>React 수업을 시작한다.</p>
        </Panel>
      );
    }

    children은 컴포넌트 태그 사이에 작성한 내용이 자동으로 전달되는 특별한 props이다.


    먼저 부모 역할을 하는 ChildrenTest는 Panel 태그 사이에 아래 내용을 넣는다.

    <Panel>
      <h2>공지사항</h2>
      <p>React 수업을 시작한다.</p>
    </Panel>

    이때 Panel 태그 사이에 있는 내용은 아래 두 개이다.

    <h2>공지사항</h2>
    <p>React 수업을 시작한다.</p>

    이 두 요소가 Panel 컴포넌트의 children 값으로 전달된다.


    즉, Panel이 받는 값은 아래와 비슷하게 이해하면 된다.

    children = (
      <>
        <h2>공지사항</h2>
        <p>React 수업을 시작한다.</p>
      </>
    );

    이제 Panel 컴포넌트를 보면 된다.

    function Panel({ children }) {
      return <section className="panel">{children}</section>;
    }

    Panel은 전달받은 children 값을 <section> 안의 {children} 위치에 넣는다.
    그래서 최종 구조는 아래처럼 만들어진다.

    <section className="panel">
      <h2>공지사항</h2>
      <p>React 수업을 시작한다.</p>
    </section>

    children은 컴포넌트 태그 사이에 작성한 내용을 다른 컴포넌트 안으로 그대로 옮겨 넣는 역할을 한다.


    16-3. 결과물 확인

    컴포넌트 태그 사이에 작성한 내용은 children이라는 특별한 props로 전달된다.


    결과물에서는 Panel 내부에 공지사항과 React 수업을 시작한다.가 출력된다.
    즉, 태그 사이에 작성한 내용이 {children} 위치에 들어간 것이다.


    정리하면 children은 컴포넌트의 공통 틀은 유지하면서 내부 내용만 바꿔 넣고 싶을 때 사용한다.


    16-4. 추가 응용 예시

    이번에는 Card 컴포넌트를 만들어 본다.
    카드의 겉 틀은 Card가 담당하고, 내부 내용은 children으로 받는다.

    // CardChildrenApplyExample.jsx
    function Card({ children }) {
      return (
        <section className="card">
          {children}
        </section>
      );
    }
    
    export default function CardChildrenApplyExample() {
      return (
        <Card>
          <h2>회원 정보</h2>
          <p>이름: 둘리</p>
          <button>수정하기</button>
        </Card>
      );
    }
    // 출력결과
    // 회원 정보
    // 이름: 둘리
    // 수정하기

    이 예시는 Card 컴포넌트를 공통 레이아웃처럼 사용한다.
    안에 들어가는 제목, 설명, 버튼은 부모가 자유롭게 작성한다.


    그래서 children을 사용하면 카드, 모달, 패널 같은 재사용 가능한 컴포넌트를 만들기 좋다.




    EduApp4.jsx: state와 이벤트 처리 예제

    EduApp4.jsx는 useState()를 사용해 화면에 영향을 주는 값을 관리하고, 버튼 이벤트로 상태를 변경하는 예제이다.
    state는 컴포넌트 내부에서 직접 관리하는 변경 가능한 데이터이다.


    버튼 클릭으로 값이 바뀌고, 바뀐 값이 화면에 다시 표시된다면 state를 사용해야 한다.
    이 예제에서는 숫자 상태, 객체 상태, 배열 상태를 각각 변경하는 흐름을 확인한다.



    17. Counter: useState로 숫자 상태 변경하기

    17-1. 개념 복습

    useState()는 함수형 컴포넌트에서 state를 만들 때 사용하는 Hook이다.
    Hook은 함수형 컴포넌트에서 React 기능을 사용할 수 있게 해주는 특별한 함수이다.


    useState(0)을 호출하면 현재 상태 값과 그 값을 변경하는 함수가 함께 만들어진다.
    보통 const [count, setCount] = useState(0)처럼 작성한다.


    count는 현재 값이고, setCount는 값을 바꾸도록 React에게 요청하는 함수이다.


    17-2. 코드 흐름

    // EduApp4.jsx
    import { useState } from 'react';
    
    export function Counter() {
      const [count, setCount] = useState(0);
      // count에는 처음에 숫자 0이 저장된다.
      // setCount는 count 값을 새 값으로 바꾸도록 React에 요청하는 함수이다.
    
      return (
        <section>
          <p>현재 값: {count}</p>
          {/* 처음에는 count 값 0이 {count} 위치에 들어가 "현재 값: 0"이 출력된다. */}
    
          <button onClick={() => setCount(count + 1)}>증가</button>
          {/* 버튼을 클릭하면 현재 count 값에 1을 더한 값이 setCount로 전달된다. */}
          {/* count가 0이면 setCount(1)이 실행되고, 화면은 "현재 값: 1"로 다시 렌더링된다. */}
        </section>
      );
    }

    처음 렌더링될 때 count의 값은 0이다.
    그래서 화면에는 현재 값: 0이 출력된다.


    버튼을 클릭하면 onClick에 연결된 화살표 함수가 실행된다.
    그 안에서 setCount(count + 1)이 호출된다.
    그러면 count 값이 1 증가하고, React가 화면을 다시 렌더링한다.


    17-3. 결과물 확인

    setCount()로 상태 변경을 요청하면 React가 변경된 count 값을 기준으로 화면을 다시 렌더링한다.


    결과물에서는 처음에 현재 값: 0이 보이고, 증가 버튼을 누를 때마다 값이 1씩 증가한다.
    이 화면 변화는 일반 변수를 바꿔서 생기는 것이 아니라, setCount()로 state 변경을 요청했기 때문에 일어난다.


    state 변경 함수가 호출되면 React는 변경된 값을 기준으로 화면을 다시 렌더링한다.


    17-4. 추가 응용 예시

    이번에는 증가 버튼과 감소 버튼을 함께 만들어 본다.
    하나의 state 값을 여러 이벤트로 변경할 수 있다.

    // CounterPlusMinusExample.jsx
    import { useState } from 'react';
    
    export default function CounterPlusMinusExample() {
      const [count, setCount] = useState(0);
    
      return (
        <section>
          <p>현재 값: {count}</p>
          <button onClick={() => setCount(count + 1)}>증가</button>
          <button onClick={() => setCount(count - 1)}>감소</button>
        </section>
      );
    }
    // 초기 출력결과
    // 현재 값: 0
    
    // 증가 버튼 클릭 후
    // 현재 값: 1
    
    // 감소 버튼 클릭 후
    // 현재 값: 0

    이 예시에서는 증가 버튼과 감소 버튼이 같은 count 값을 바꾼다.
    setCount(count + 1)은 값을 증가시키고, setCount(count - 1)은 값을 감소시킨다.


    하나의 state를 여러 이벤트에서 변경할 수 있다는 점을 확인할 수 있다.



    18. StateTest: 객체 state 변경

    18-1. 개념 복습

    객체를 state로 관리할 때는 기존 객체를 직접 수정하지 않는다.
    기존 객체를 직접 바꾸면 React가 변경을 안정적으로 인식하기 어렵다.


    그래서 객체 state를 변경할 때는 기존 객체를 복사한 새 객체를 만든다.
    이때 스프레드 연산자 ...를 자주 사용한다.


    예를 들어 { ...user, name: '또치' }는 기존 user 객체의 내용을 복사하고, name만 새 값으로 덮어쓴 새 객체이다.


    18-2. 코드 흐름

    // EduApp4.jsx
    import { useState } from 'react';
    
    export function StateTest() {
      const [user, setUser] = useState({ name: '둘리', age: 10 });
    
      const updateUserName = () => {
        // 기존 user를 펼치고 name만 덮어쓴다.
        const nextUser = { ...user, name: '또치' };
        setUser(nextUser);
      };
    
      const increaseUserAge = () => {
        // 기존 user를 펼치고 age만 1 증가시킨다.
        const nextUser = { ...user, age: user.age + 1 };
        setUser(nextUser);
      };
    
      return (
        <div style={{ padding: '20px' }}>
          <h1>React State 테스트</h1>
    
          <section style={{ marginBottom: '30px', borderBottom: '1px solid #ccc' }}>
            <h2>객체 상태 변경</h2>
    
            <p>이름: {user.name} / 나이: {user.age}</p>
    
            <button onClick={updateUserName}>이름 변경 (또치)</button>
            <button onClick={increaseUserAge}>나이 증가</button>
          </section>
        </div>
      );
    }

    이 코드는 객체 state인 user를 변경하는 흐름을 보기 쉽게 정리한 예시이다.
    처음 user 값은 { name: '둘리', age: 10 }이다.
    그래서 화면에는 이름: 둘리 / 나이: 10이 출력된다.


    객체 state를 바꿀 때 핵심은 기존 객체를 직접 고치지 않는 것이다.
    기존 객체를 펼쳐서 새 객체를 만들고, 바꾸고 싶은 속성만 뒤에서 덮어쓴다.


    이름 변경 흐름

    이름 변경 (또치) 버튼에는 updateUserName 함수가 연결되어 있다.

    <button onClick={updateUserName}>이름 변경 (또치)</button>

    버튼을 클릭하면 아래 함수가 실행된다.

    const updateUserName = () => {
      const nextUser = { ...user, name: '또치' };
      setUser(nextUser);
    };

    여기서 가장 중요한 코드는 아래 한 줄이다.

    const nextUser = { ...user, name: '또치' };

    현재 user 값이 아래와 같다고 생각하면 된다.

    { name: '둘리', age: 10 }

    먼저 ...user는 기존 user 객체 안의 값을 새 객체 안으로 펼친다.

    { ...user }

    이 단계의 결과는 아래와 같다.

    { name: '둘리', age: 10 }

    그 다음 뒤에 name: '또치'가 붙는다.

    { ...user, name: '또치' }

    객체에서 같은 속성 이름이 뒤에 다시 나오면, 뒤에 있는 값이 앞의 값을 덮어쓴다.
    그래서 name: '둘리'가 name: '또치'로 바뀐다.

    { name: '둘리', age: 10, name: '또치' }

    위 흐름은 실제로는 아래 객체로 정리된다.

    { name: '또치', age: 10 }

    따라서 nextUser에는 아래 객체가 저장된다.

    const nextUser = { name: '또치', age: 10 };

    이 새 객체를 setUser()에 전달한다.

    setUser(nextUser);

    그러면 React는 user 값이 새 객체로 바뀌었다고 판단하고 화면을 다시 렌더링한다.

    // 출력결과
    // 이름: 또치 / 나이: 10

    이 흐름에서 age는 바뀌지 않는다.
    ...user로 기존 age: 10을 복사했고, 뒤에서 age를 다시 덮어쓰지 않았기 때문이다.


    나이 증가 흐름

    나이 증가 버튼에는 increaseUserAge 함수가 연결되어 있다.

    <button onClick={increaseUserAge}>나이 증가</button>

    버튼을 클릭하면 아래 함수가 실행된다.

    const increaseUserAge = () => {
      const nextUser = { ...user, age: user.age + 1 };
      setUser(nextUser);
    };

    여기서 가장 중요한 코드는 아래 한 줄이다.

    const nextUser = { ...user, age: user.age + 1 };

    현재 user 값이 아래와 같다고 생각하면 된다.

    { name: '둘리', age: 10 }

    먼저 ...user는 기존 user 객체 안의 값을 새 객체 안으로 펼친다.

    { ...user }

    이 단계의 결과는 아래와 같다.

    { name: '둘리', age: 10 }

    그 다음 뒤에 age: user.age + 1이 붙는다.

    { ...user, age: user.age + 1 }

    현재 user.age는 10이다.
    그래서 user.age + 1은 11이 된다.

    age: 10 + 1

    계산 결과는 아래와 같다.

    age: 11

    객체에서 같은 속성 이름이 뒤에 다시 나오면, 뒤에 있는 값이 앞의 값을 덮어쓴다.
    그래서 age: 10이 age: 11로 바뀐다.

    { name: '둘리', age: 10, age: 11 }

    위 흐름은 실제로는 아래 객체로 정리된다.

    { name: '둘리', age: 11 }

    따라서 nextUser에는 아래 객체가 저장된다.

    const nextUser = { name: '둘리', age: 11 };

    이 새 객체를 setUser()에 전달한다.

    setUser(nextUser);

    그러면 React는 변경된 user 값을 기준으로 화면을 다시 렌더링한다.

    // 출력결과
    // 이름: 둘리 / 나이: 11

    이 흐름에서 name은 바뀌지 않는다.
    ...user로 기존 name: '둘리'를 복사했고, 뒤에서 name을 다시 덮어쓰지 않았기 때문이다.


    만약 이름 변경 (또치) 버튼을 먼저 누른 상태라면 현재 user의 name은 또치이다.
    그 상태에서 나이 증가 버튼을 누르면 name은 또치로 유지되고, age만 증가한다.

    // 이름 변경 후 나이 증가 버튼 클릭 결과
    // 이름: 또치 / 나이: 11

    객체 state에서 특정 속성만 바꾸면, 나머지 속성은 ...user로 복사된 기존 값이 그대로 유지된다.


    18-3. 결과물 확인

    객체 state를 변경할 때는 기존 객체를 직접 고치지 않고, 스프레드 연산자로 복사한 새 객체를 전달한다.


    결과물에서는 처음에 이름: 둘리 / 나이: 10이 보인다.
    이름 변경 (또치) 버튼을 누르면 이름만 또치로 바뀌고, 나이는 그대로 유지된다.
    나이 증가 버튼을 누르면 이름은 유지되고 나이만 증가한다.


    즉, { ...user, name: '또치' }와 { ...user, age: user.age + 1 }은 기존 객체 전체를 직접 수정하는 것이 아니라, 기존 값을 복사한 새 객체를 만들어 필요한 속성만 바꾸는 방식이다.


    18-4. 추가 응용 예시

    이번에는 user 객체에 address 값을 추가해 본다.
    기존 name과 age는 유지하면서 address만 변경하는 흐름이다.

    // ObjectStateAddressExample.jsx
    import { useState } from 'react';
    
    export default function ObjectStateAddressExample() {
      const [user, setUser] = useState({
        name: '둘리',
        age: 10,
        address: '쌍문동',
      });
    
      return (
        <section>
          <p>이름: {user.name}</p>
          <p>나이: {user.age}</p>
          <p>주소: {user.address}</p>
          <button onClick={() => setUser({ ...user, address: '고길동 집' })}>
            주소 변경
          </button>
        </section>
      );
    }
    // 초기 출력결과
    // 이름: 둘리
    // 나이: 10
    // 주소: 쌍문동
    
    // 주소 변경 버튼 클릭 후
    // 이름: 둘리
    // 나이: 10
    // 주소: 고길동 집

    setUser({ ...user, address: '고길동 집' })는 기존 user 객체를 복사한 뒤 address만 바꾼다.
    그래서 name과 age는 그대로 유지된다.


    객체 state를 바꿀 때는 바꾸고 싶은 속성만 덮어쓴 새 객체를 만들어 전달해야 한다.



    19. StateTest: 배열 state 변경

    19-1. 개념 복습

    배열을 state로 관리할 때도 기존 배열을 직접 수정하지 않는다.
    예를 들어 items.push('포도')처럼 기존 배열을 직접 바꾸는 방식은 피해야 한다.


    대신 새 배열을 만들어 setItems()에 전달한다.
    배열에 값을 추가할 때는 [...items, '포도']처럼 작성할 수 있다.
    배열에서 마지막 값을 제거할 때는 items.slice(0, -1)처럼 새 배열을 만들 수 있다.


    19-2. 코드 흐름

    // EduApp4.jsx
    import { useState } from 'react';
    
    export function StateTest() {
      const [items, setItems] = useState(['사과', '바나나']);
    
      const addItem = () => {
        const nextItems = [...items, '포도'];
        setItems(nextItems);
      };
    
      const removeLastItem = () => {
        const nextItems = items.slice(0, -1);
        setItems(nextItems);
      };
    
      return (
        <section>
          <h2>배열 상태 변경</h2>
    
          <ul>
            {items.map((item, index) => {
              return <li key={index}>{item}</li>;
            })}
          </ul>
    
          <button onClick={addItem}>항목 추가 (포도)</button>
          <button onClick={removeLastItem}>마지막 항목 삭제</button>
        </section>
      );
    }

    이 코드는 배열 state인 items를 변경하는 흐름을 보기 쉽게 정리한 예시이다.
    처음 items 값은 ['사과', '바나나']이다.
    그래서 화면에는 사과, 바나나가 목록으로 출력된다.


    배열 state를 바꿀 때 핵심은 기존 배열을 직접 고치지 않는 것이다.
    기존 배열을 펼쳐 새 배열을 만들거나, 기존 배열에서 일부를 잘라낸 새 배열을 만들어 setItems()에 전달해야 한다.


    항목 추가 흐름

    항목 추가 (포도) 버튼에는 addItem 함수가 연결되어 있다.

    <button onClick={addItem}>항목 추가 (포도)</button>

    버튼을 클릭하면 아래 함수가 실행된다.

    const addItem = () => {
      const nextItems = [...items, '포도'];
      setItems(nextItems);
    };

    여기서 가장 중요한 코드는 아래 한 줄이다.

    const nextItems = [...items, '포도'];

    현재 items 값이 아래와 같다고 생각하면 된다.

    ['사과', '바나나']

    먼저 ...items는 기존 items 배열 안의 값을 새 배열 안으로 펼친다.

    [...items]

    이 단계의 결과는 아래와 같다.

    ['사과', '바나나']

    그 다음 뒤에 '포도'가 붙는다.

    [...items, '포도']

    그러면 새 배열은 아래처럼 만들어진다.

    ['사과', '바나나', '포도']

    따라서 nextItems에는 아래 배열이 저장된다.

    const nextItems = ['사과', '바나나', '포도'];

    이 새 배열을 setItems()에 전달한다.

    setItems(nextItems);

    그러면 React는 items 값이 새 배열로 바뀌었다고 판단하고 화면을 다시 렌더링한다.

    // 출력결과
    // 사과
    // 바나나
    // 포도



    마지막 항목 삭제 흐름

    마지막 항목 삭제 버튼에는 removeLastItem 함수가 연결되어 있다.

    <button onClick={removeLastItem}>마지막 항목 삭제</button>

    버튼을 클릭하면 아래 함수가 실행된다.

    const removeLastItem = () => {
      const nextItems = items.slice(0, -1);
      setItems(nextItems);
    };

    여기서 가장 중요한 코드는 아래 한 줄이다.

    const nextItems = items.slice(0, -1);

    현재 items 값이 아래와 같다고 생각하면 된다.

    ['사과', '바나나', '포도']

    slice(0, -1)은 처음부터 마지막 바로 전까지의 값을 잘라 새 배열을 만든다.

    items.slice(0, -1)

    이 단계의 결과는 아래와 같다.

    ['사과', '바나나']

    따라서 nextItems에는 아래 배열이 저장된다.

    const nextItems = ['사과', '바나나'];

    이 새 배열을 setItems()에 전달한다.

    setItems(nextItems);

    그러면 React는 items 값이 새 배열로 바뀌었다고 판단하고 화면을 다시 렌더링한다.

    // 출력결과
    // 사과
    // 바나나



    map으로 화면에 출력되는 흐름

    items 배열은 map()을 통해 화면에 출력된다.

    {items.map((item, index) => {
      return <li key={index}>{item}</li>;
    })}

    처음 items 값이 아래와 같다면,

    ['사과', '바나나']

    첫 번째 반복에서 item에는 '사과'가 들어온다.
    index에는 0이 들어온다.

    <li key={0}>사과</li>

    두 번째 반복에서 item에는 '바나나'가 들어온다.
    index에는 1이 들어온다.

    <li key={1}>바나나</li>

    이때 key는 React가 반복 항목을 구분하기 위해 내부에서 사용하는 값이다.
    브라우저 화면에 직접 보이는 값은 아니다.


    실제 화면에는 아래처럼 항목 내용만 출력된다.

    // 출력결과
    // 사과
    // 바나나

    항목 추가 (포도) 버튼을 눌러 items가 ['사과', '바나나', '포도']가 되면, map()은 세 번째 반복에서 '포도'도 출력한다.

    <li key={2}>포도</li>

    실제 화면에는 아래처럼 보인다.

    // 출력결과
    // 사과
    // 바나나
    // 포도

    배열 state를 변경할 때는 기존 배열을 직접 고치지 않고, 새 배열을 만들어 setItems()에 전달해야 한다.


    19-3. 결과물 확인

    배열 state를 변경할 때도 기존 배열을 직접 수정하지 않고, 새 배열을 만들어 상태 변경 함수에 전달해야 한다.


    결과물에서는 처음에 사과, 바나나가 보인다.
    항목 추가 (포도) 버튼을 누르면 기존 항목 뒤에 포도가 추가된다.
    마지막 항목 삭제 버튼을 누르면 배열의 마지막 항목이 제거된다.


    이 흐름에서 핵심은 items 배열을 직접 수정하지 않는다는 점이다.
    [...items, '포도']는 새 항목이 추가된 새 배열을 만들고, items.slice(0, -1)은 마지막 항목을 제외한 새 배열을 만든다.


    배열 state도 기존 배열을 직접 고치는 것이 아니라 새 배열을 만들어 변경해야 한다.


    19-4. 추가 응용 예시

    이번에는 입력창에 적은 값을 배열에 추가하는 예시를 만든다.
    이 예시는 입력값 state와 배열 state를 함께 사용한다.

    // ArrayStateInputAddExample.jsx
    import { useState } from 'react';
    
    export default function ArrayStateInputAddExample() {
      const [text, setText] = useState('');
      const [items, setItems] = useState(['사과', '바나나']);
    
      function addItem() {
        if (text.trim() === '') {
          return;
        }
    
        const nextItems = [...items, text];
        setItems(nextItems);
        setText('');
      }
    
      return (
        <section>
          <input
            value={text}
            onChange={(event) => setText(event.target.value)}
            placeholder="추가할 항목 입력"
          />
          <button onClick={addItem}>추가</button>
    
          <ul>
            {items.map((item, index) => {
              return <li key={index}>{item}</li>;
            })}
          </ul>
        </section>
      );
    }
    // 출력결과
    // 사과
    // 바나나
    
    // 입력창에 포도 입력 후 추가 버튼 클릭
    // 사과
    // 바나나
    // 포도

    이 예시는 입력창의 값과 목록 배열을 각각 state로 관리한다.
    text는 입력창에 현재 적힌 값을 저장한다.
    items는 화면에 출력할 목록 데이터를 저장한다.


    입력값이 text에 저장되는 흐름

    입력창은 아래 코드로 만들어진다.

    <input
      value={text}
      onChange={(event) => setText(event.target.value)}
      placeholder="추가할 항목 입력"
    />

    처음 text 값은 빈 문자열이다.

    const [text, setText] = useState('');

    그래서 입력창도 처음에는 비어 있다.


    사용자가 입력창에 포도를 입력하면 onChange가 실행된다.

    onChange={(event) => setText(event.target.value)}

    이때 event.target.value에는 사용자가 입력한 문자열이 들어 있다.

    event.target.value

    사용자가 포도를 입력했다면 값은 아래와 같다.

    '포도'

    이 값이 setText()에 전달된다.

    setText('포도');

    그러면 React는 text state를 '포도'로 바꾸고 화면을 다시 렌더링한다.
    이때 결과적으로 text 값은 아래처럼 이해하면 된다.

    // 변경 후 text state 값
    // '포도'



    추가 버튼을 눌렀을 때 흐름

    추가 버튼에는 addItem 함수가 연결되어 있다.

    <button onClick={addItem}>추가</button>

    버튼을 클릭하면 아래 함수가 실행된다.

    function addItem() {
      if (text.trim() === '') {
        return;
      }
    
      const nextItems = [...items, text];
      setItems(nextItems);
      setText('');
    }

    먼저 입력값이 비어 있는지 검사한다.

    if (text.trim() === '') {
      return;
    }

    text가 '포도'라면 빈 문자열이 아니므로 아래 코드로 넘어간다.

    const nextItems = [...items, text];

    현재 items 값이 아래와 같다고 생각하면 된다.

    ['사과', '바나나']

    현재 text 값은 아래와 같다.

    '포도'

    ...items는 기존 배열의 값을 새 배열 안으로 펼친다.

    [...items]

    이 단계의 결과는 아래와 같다.

    ['사과', '바나나']

    그 뒤에 text 값인 '포도'가 붙는다.

    [...items, text]

    그래서 nextItems는 아래 배열이 된다.

    ['사과', '바나나', '포도']

    이 새 배열을 setItems()에 전달한다.

    setItems(nextItems);

    그러면 화면 목록은 아래처럼 바뀐다.

    // 출력결과
    // 사과
    // 바나나
    // 포도

    마지막으로 setText('')가 실행된다.

    setText('');

    이 코드는 입력창의 값을 다시 빈 문자열로 바꾼다.
    그래서 항목을 추가한 뒤 입력창이 비워진다.


    입력값을 배열에 추가할 때는 입력값 state와 배열 state가 함께 움직인다. 입력창에 적은 값은 text에 저장되고, 버튼을 누르면 그 text 값이 새 배열에 추가된다.

    0개의 댓글