본문 바로가기

클라우드(Cloud)

[스나이퍼팩토리] 카카오클라우드 AIaaS 마스터 클래스 8주차 - 리액트 1 (React 1)

 

 

 지난 글까지 HTML과 JavaScript, CSS를 공부하며 실습을 함께 진행해보았다. HTML과 CSS를 이용해 토스 페이지를 클론 코딩하는 실습을 진행하고 있으며, JavaScript를 이용해 애니메이션 등의 기능을 구현하는 것도 함께 학습하고 있다. 

 

 하지만 HTML과 CSS로 구성된 페이지를 그대로 사용하지 않고 React로 변환하는 과정을 가지려 한다. 그 이유는 React로 변환하여 사용하면 컴포넌트 구조를 가지기에 재사용이 가능하다는 장점을 살릴 수 있고, 상태(state)를 기반으로 동적으로 UI를 바꿀 수 있다는 장점도 사용할 수 있으며, Virtual DOM이라는 가상의 DOM 구조를 사용해서 변경된 부분만 빠르게 업데이트 하는 장점도 살리며 사용자 경험(UX)도 향상시킬 수 있기 때문이다. 그리고 라우팅 기능과 SPA(Single Page Application)의 구성으로 사용자 입장에서 페이지 전환을 빠르고 부드럽게 느낄 수 있도록 할 수 있기에 리액트를 사용한다. 

 

 그렇기에 React를 학습하고 실습도 학습하며 진행해볼 것이다. 먼저 React에 대해 자세히 알아보도록 하자. 

 


✅ 1. 엘리먼트(Element)란?

🔹 엘리먼트란?

  • React에서 UI를 구성하는 최소 단위이자 화면에 표시할 내용을 기술하는 객체
  • HTML의 <div>, <h1>, <span> 같은 태그들을 JS 객체로 표현한 게 React 엘리먼트라고 생각하면 된다. 
  • JSX 문법을 쓰면 React.createElement() 호출 없이도 쉽게 엘리먼트를 만들 수 있다. 
const element = <h1>Hello, world!</h1>;

 

 위 코드는 내부적으로 아래와 같이 변환된다. 

const element = React.createElement('h1', null, 'Hello, world!');

 

즉, React 엘리먼트는 불변객체이며, 화면에 어떻게 보여줄지를 React에게 알려주는 "스냅샷" 이라고 볼 수 있을 것이다. 

 

✅ 2. 엘리먼트를 DOM에 렌더링하기

🔹 기본 렌더링 방법

const element = <h1>Hello, React!</h1>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

📌 동작 순서

  1. element는 JSX → JS 객체로 변환됨
  2. ReactDOM.createRoot()로 root DOM 컨테이너와 연결
  3. render()가 실행되면, 해당 element가 실제 DOM에 반영됨

 

✅ 3. 엘리먼트 업데이트 (시간, 데이터 갱신 등)

 

React 엘리먼트는 불변 객체이므로, 한번 만든 element를 직접 수정하지 않는다. 대신 새로운 element를 만들고 다시 render()를 해줘야 한다. 

 

예시: 시계 구현

function tick() {
  const element = (
    <div>
      <h1>현재 시간:</h1>
      <h2>{new Date().toLocaleTimeString()}</h2>
    </div>
  );

  const root = ReactDOM.createRoot(document.getElementById('root'));
  root.render(element);
}

setInterval(tick, 1000);
 

setInterval()을 사용해 1초마다 새로운 엘리먼트를 만들고 다시 렌더링하는 방식을 사용하였다. 

 

✅ 4. 실무에서의 렌더링 전략 요약

전략 설명 예시
초기 렌더링 페이지 로딩 시 초기 UI 생성 root.render(<App />)
수동 렌더링 일정 주기 혹은 이벤트 발생 시 다시 렌더 setInterval()로 갱신
상태 기반 렌더링 state 변화로 자동 갱신 useState, useEffect 사용
조건부 렌더링 상황에 따라 다른 요소 렌더 if, ternary, &&
 

✅ 5. 엘리먼트 렌더링의 핵심 특징

  • 👉 JSX를 통해 선언적으로 작성
  • 👉 React는 virtual DOM을 비교하여 효율적으로 업데이트
  • 👉 상태(state)나 props가 바뀌면 컴포넌트를 새롭게 렌더링
  • 👉 엘리먼트 자체는 불변 → 새로 만들어서 교체하는 방식 사용

 React 엘리먼트 렌더링에 대한 설명이 부족했던 것 같아서 추가로 자세히 학습해보려 한다. 위의 내용과 겹치는 것들이 있을 수 있으나 다시 학습한다는 생각으로 적어보려 한다. 

 

✅ React 엘리먼트 렌더링 (Element Rendering)

📌 1. DOM에 렌더링

 

 React는 브라우저의 실제 DOM에 렌더링을 담당하는 ReactDOM 라이브러리를 통해 엘리먼트를 렌더링한다. 

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

단계 설명
1 element 생성: JSX → React 엘리먼트 객체
2 ReactDOM.createRoot()로 루트 DOM에 연결
3 render()를 통해 화면에 실제 반영됨
 

📌 2. 엘리먼트는 "불변 객체"

  • React 엘리먼트는 한 번 생성하면 내용을 직접 바꿀 수 없다. 
  • 화면을 업데이트하려면, 새로운 엘리먼트를 새로 생성해서 render() 해야 한다. 

❌ 잘못된 방식 (불가능)

element.type = 'h2'; // 이렇게 수정 불가!

 

✅ 올바른 방식 (새로 생성)

const newElement = <h2>Updated!</h2>;
root.render(newElement);
 

🔁 즉, React의 업데이트 방식은 "직접 수정"이 아니라 "새로 만들고 교체"하는 방식으로 이루어지는 것이다. 

 

📌 3. 수동 렌더링 예시: 실시간 시계

function tick() {
  const element = (
    <div>
      <h1>현재 시각</h1>
      <h2>{new Date().toLocaleTimeString()}</h2>
    </div>
  );
  const root = ReactDOM.createRoot(document.getElementById('root'));
  root.render(element);
}

setInterval(tick, 1000); // 매초 새 엘리먼트 렌더링

tick() 함수가 매초마다 새로운 엘리먼트를 만들고, 그걸 다시 렌더링함.
이처럼 매번 "새로 만들어서 교체"하는 게 React의 기본 흐름이야.

 

📌 4. 엘리먼트 vs 컴포넌트의 렌더링 차이

비교 항목 엘리먼트 렌더링 컴포넌트 렌더링
방식 ReactDOM.render()로 직접 컴포넌트 내부에서 자동 처리
상태 업데이트 없음 (불변) → 수동 렌더 상태 변경 → 자동 재렌더링
실무 활용도 거의 사용 안 함 99% 실무에선 컴포넌트 사용
 

📌 5. 실무 렌더링 전략 정리

상황  방법 설명
초기 페이지 렌더링 root.render(<App />) 첫 화면 표시
주기적 업데이트 setInterval() → render() 실시간 시계처럼 직접 렌더링
상태 기반 동적 렌더링 useState, useEffect 값이 변하면 React가 자동 갱신
조건부 렌더링 JSX 내 조건 표현식 if, &&, 삼항 연산자 등 사용

 

 

 이렇게 렌더링에 대해 학습해보았고, 이제는 React 앱을 이루는 UI의 부분이라고 할 수 있고, 독립적이라 재사용이 가능한 단위를 말하는데 HTML 태그처럼 사용하지만, 내부에 JS 로직과 상태를 가질 수 있기에 신경써서 학습해볼 것이다. 

 

✅ 1. 컴포넌트(Component)란?

🔹 정의

  • React 앱을 이루는 UI의 조각(부분)이자 독립적이고 재사용 가능한 단위
  • HTML 태그처럼 사용하지만, 내부에 JS 로직과 상태를 가질 수 있음

🔹 두 가지 방식

방식 설명 예시
함수형 컴포넌트 React 16.8 이후 표준 function Welcome() {}
클래스형 컴포넌트 과거 방식 (지양) class Welcome extends React.Component
 

🔹 예시: 함수형 컴포넌트

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

 

사용할 땐 HTML 태그처럼 사용한다. 

<Welcome name="이름" />

 

✅ 2. props: 컴포넌트의 입력값

  • 부모 → 자식 컴포넌트로 전달하는 읽기 전용 데이터
  • 컴포넌트를 동적으로 재사용할 수 있도록 도와줌

예시:

function Profile(props) {
  return <p>이름: {props.name}</p>;
}

<Profile name="이름" />

 

✅ 3. 상태(state): 컴포넌트 내부의 변화 관리

🔹 정의

  • 컴포넌트 내부에서 유지되는 값이며, 변경되면 자동으로 렌더링이 일어남
  • useState 훅을 통해 정의함

🔹 예시: 클릭 수 증가 버튼

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // 상태 정의

  return (
    <div>
      <p>현재 클릭 수: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

코드 요소 설명
useState(0) 초기값 0으로 상태 변수 count 생성
setCount() 상태를 변경하고, 변경되면 자동 렌더링
onClick 이벤트가 발생하면 setCount 호출
 

✅ 4. 컴포넌트 + 상태 = 자동 렌더링

🔄 흐름 요약

  1. 컴포넌트 내부에서 상태(state)를 정의
  2. 사용자 입력이나 이벤트 발생
  3. setState or setCount() 호출
  4. 변경된 state로 컴포넌트 전체가 자동 재렌더링
  5. 브라우저에 변경된 UI 반영

 

✅ 5. 상태와 엘리먼트의 차이

비교 엘리먼트 렌더링 상태 기반 렌더링
방식 수동으로 render() 자동으로 업데이트
대상 특정 JSX 객체 컴포넌트 전체
예시 시계 tick() 호출 useState, setCount()
실무 활용 거의 없음 대부분 사용됨 ✅
 

✅ 6. 상태 여러 개 다루기

function Form() {
  const [name, setName] = useState('');
  const [age, setAge] = useState(0);

  return (
    <div>
      <input onChange={(e) => setName(e.target.value)} />
      <input type="number" onChange={(e) => setAge(Number(e.target.value))} />
      <p>이름: {name}, 나이: {age}</p>
    </div>
  );
}

컴포넌트 안에 여러 상태를 독립적으로 설정하고 각각 setXXX()으로 업데이트가 가능하다. 

 

✅ 7. 요약

요소 설명
컴포넌트 재사용 가능한 UI 조각
props 부모 → 자식으로 전달되는 읽기 전용 값
state 컴포넌트 내부의 변화하는 값 (자동 렌더링 트리거)
useState 상태 훅 (state 정의 + 갱신 함수 제공)
setState 상태 갱신 → 컴포넌트 자동 재렌더링
이벤트 onClick, onChange 등으로 사용자 입력 처리
 

📌 React 렌더링 전체 흐름

JSX 작성 → React 엘리먼트 생성 → 컴포넌트 생성 → props/state 설정
 → 사용자 입력 발생 → 상태 변화 (setState) → React가 Virtual DOM 비교 → 변경된 부분만 DOM 업데이트

 


📌 1. props란?

🔹 정의

  • propsproperties(속성)의 줄임말로, 부모 컴포넌트가 자식 컴포넌트에게 전달하는 데이터이다. 
  • 자식 컴포넌트는 전달받은 props를 사용해서 화면에 보여주거나 로직에 활용
  • 읽기 전용(immutable)이며, 자식 컴포넌트가 직접 수정할 수 없음

📌 2. props를 사용하는 이유

이유 설명
컴포넌트 재사용성 한 컴포넌트를 다양한 값으로 다르게 사용 가능
데이터 흐름 유지 상위 → 하위로 명확한 데이터 흐름
동적 UI 구현 동적으로 변경되는 내용을 전달하여 다양한 출력 가능
 

📌 3. 기본 사용법

부모 컴포넌트

<Welcome name="이름" />

 

자식 컴포넌트

function Welcome(props) {
  return <h1>안녕하세요, {props.name}님!</h1>;
}

 

실행 결과

안녕하세요, 이름님!

 

부모가 전달한 값 "이름"이 props.name으로 자식에게 전달되는 것을 확인할 수 있음. 

 

📌 4. 여러 개의 props 전달하기

<Profile name="이름" age={26} major="산업공학" />
function Profile(props) {
  return (
    <div>
      <p>이름: {props.name}</p>
      <p>나이: {props.age}</p>
      <p>전공: {props.major}</p>
    </div>
  );
}

 

📌 5. 구조 분해 할당(Destructuring) 방식

function Profile({ name, age, major }) {
  return (
    <div>
      <p>{name} / {age}살 / {major}</p>
    </div>
  );
}

전보다 가독성이 좋아지고 코드를 짧게 쓸 수 있는 장점이 있음

 

📌 6. props는 읽기 전용

function Wrong({ name }) {
  name = "수정불가"; // ❌ 이렇게 수정하려고 하면 안됨. 
  return <p>{name}</p>;
}

props는 컴포넌트 외부에서 전달된 "상수 같은 값"이기 때문에 내부에서 변경하면 안된다. 

 

📌 7. 동적 props 전달 예시

function App() {
  const users = ['~~', '~~', '~~'];

  return (
    <div>
      {users.map((name, index) => (
        <Welcome key={index} name={name} />
      ))}
    </div>
  );
}
function Welcome({ name }) {
  return <p>안녕하세요, {name}님!</p>;
}

배열을 map()으로 반복하며 각 사용자 이름을 props로 전달 → 동적인 컴포넌트 생성

 

📌 8. props 기본값 설정 (defaultProps)

function Welcome({ name }) {
  return <h1>안녕하세요, {name}님!</h1>;
}

Welcome.defaultProps = {
  name: "익명",
};

 

name이 안 넘어올 경우 기본값으로 "익명" 사용하도록 설정 

 

📌 9. 적용 예시

🔹 Button 컴포넌트 만들기

function Button({ label, onClick }) {
  return <button onClick={onClick}>{label}</button>;
}

🔹 사용

<Button label="제출하기" onClick={() => alert('제출')} />
<Button label="취소" onClick={() => alert('취소')} />

label과 onClick을 다르게 주면 하나의 Button 컴포넌트를 여러 상황에 재사용이 가능. 

 

📌 10. props의 흐름은 단방향 → "단방향 바인딩"

React는 데이터 흐름이 한 방향으로만 흐른다. 

[부모 컴포넌트] → [자식 컴포넌트]

자식은 props를 통해 전달받기만 할 뿐, 부모의 값을 직접 바꿀 수는 없다. 
(자식에서 값을 바꾸고 싶으면 이벤트 콜백을 부모에 전달해서 부모가 바꿔줘야 함)

 


 위 내용 중 props의 흐름은 단방향이라고 설명한 후에 간단하게 React의 데이터 흐름은 부모 컴포넌트에서 자식 컴포넌트로만 흐르고, 자식 컴포넌트에서 부모 컴포넌트로 보낼 때는 이벤트 콜백을 사용해야 한다고 설명하였다. 이 부분이 이해가잘 되지 않아 조금 더 자세히 정리하며 학습해보려 한다. 

 

✅ 컴포넌트 간 통신 (자식 → 부모 방향)

✅ 1. React의 데이터 흐름 원칙

 

📌 단방향 데이터 흐름 (Unidirectional Data Flow)

  • React는 데이터를 아래로만 내려보냄: 부모 → 자식
  • 자식이 부모에게 데이터를 직접 전달하거나 수정할 수는 없음

그래서 자식 → 부모 통신을 하려면 어떻게 해야 할까?

👉 부모가 함수를 정의하고, 그 함수를 자식에게 props로 넘긴 후

👉 자식은 그 함수를 호출하며 데이터를 전달하는 방식이야.

✅ 2. 흐름 개념도

[Parent Component]
     ↑     (콜백 함수 호출)
[Child Component]
  • 부모가 자식에게 콜백 함수를 props로 전달
  • 자식이 이벤트 발생 시 부모 함수 호출하면서 값 전달
  • 부모는 전달받은 값을 상태(state)로 저장하거나 다른 로직 실행

✅ 3. 실습 예제

🔸 부모 컴포넌트

import React, { useState } from 'react';
import Child from './Child';

function Parent() {
  const [message, setMessage] = useState('');

  // 자식이 호출할 함수
  const handleMessage = (msg) => {
    setMessage(msg);
  };

  return (
    <div>
      <h2>부모 컴포넌트</h2>
      <p>자식에게 받은 메시지: {message}</p>
      <Child onSend={handleMessage} />
    </div>
  );
}

 

🔸 자식 컴포넌트 (Child.js)

function Child({ onSend }) {
  return (
    <div>
      <h3>자식 컴포넌트</h3>
      <button onClick={() => onSend('안녕하세요! 부모님')}>
        부모에게 메시지 보내기
      </button>
    </div>
  );
}

✅ 4. 흐름 정리

단계 설명
1 부모가 handleMessage 함수를 정의
2 이 함수를 props로 자식에게 넘김 → <Child onSend={handleMessage} />
3 자식은 onSend()를 클릭 이벤트에서 호출
4 부모 함수가 실행되고, 상태가 갱신됨 → 자동 렌더링

 

✅ 5. 규칙 정리

규칙 설명
데이터 흐름은 단방향 부모 → 자식 (props), 자식 → 부모 (콜백 호출)
자식은 props를 직접 수정 ❌ 오직 "부모에게 알리기"만 가능
부모가 상태를 갖고 있어야 통제 가능 자식의 이벤트로 상태 변경하려면 부모가 state를 정의해야 함
복잡한 구조에서는 상태 끌어올리기(Lifting State Up)가 필요 공통 부모로 상태를 올려서 관리해야 함
 

✅ 핵심 요약

  • 자식 컴포넌트에서 부모로 데이터를 전달하려면 "부모가 정의한 함수(props로 넘긴 것)를 자식이 호출"해야 한다.
  • 이 방식을 통해 React의 단방향 데이터 흐름 원칙을 유지하면서도 상호작용이 가능한 유동적인 앱을 만들 수 있다.

 지금까지의 내용을 이해하기 위해서는 이제부터 설명할 개념인 상태(State)도 알아야 할 것이다. 상태(State)를 이해해야 동적인 UI, 사용자 입력 처리, 컴포넌트 간 상호작용을 모두 잘 구성할 수 있을 것이다. 

 

✅ 1. State란 무엇인가?

항목 설명
정의 컴포넌트 내부에서 관리되는 동적인 데이터
특징 값이 바뀌면 자동으로 해당 컴포넌트가 리렌더링됨
사용 목적 사용자 입력, 타이머, 체크박스 상태 등 변화에 반응하는 UI를 구현할 때 사용
 

✅ 2. State와 Props 비교

항목 Props State
정의 부모 → 자식으로 전달되는 값 컴포넌트 내부에서 선언하고 관리하는 값
수정 가능 여부 읽기 전용 setState() 또는 setX()로 변경 가능
목적 외부 데이터 전달 내부 동적 데이터 저장 및 변경
 

✅ 3. useState 기본 문법

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);  // 상태 선언

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

useState(초기값)을 통해 상태값과 변경 함수(set 함수)를 함께 반환받는다.

 

✅ 4. 상태 업데이트와 자동 렌더링

setCount(count + 1);
  • 상태를 변경하면, React는 자동으로 해당 컴포넌트를 다시 렌더링
  • 화면은 항상 최신 상태를 반영하게 된다.

 

✅ 5. 상태 변경은 "비동기적"으로 처리됨

setCount(count + 1);
setCount(count + 1); // 연속 두 번 실행해도 1만 증가할 수 있음

 

 해결 방법: 이전 값을 기반으로 업데이트하려면 함수형 업데이트 사용

setCount((prev) => prev + 1);
setCount((prev) => prev + 1); // 이렇게 하면 2가 증가됨!

 

✅ 6. State 올바른 사용법 

1. 상태는 컴포넌트 내에서만 정의한다

// ❌ 컴포넌트 외부에서 선언하면 안 됨!
let count = 0; // 이렇게 하면 React가 상태 변화를 감지하지 못함
  • 컴포넌트 외부에서 값을 정의하면 렌더링과 무관한 일반 변수로 설정된다. 
  • 반드시 useState()로 컴포넌트 내부에서 선언해야 함. 

2. 상태 변경은 반드시 setX() 함수로

const [name, setName] = useState('~~');

// ❌ 이렇게 직접 변경하면 안 됨
name = '~~'; // 상태가 바뀌지 않음!

// ✅ setName()을 사용해야 함
setName('~~');
  • React는 set 함수 호출을 통해서만 상태 변화를 감지하고 다시 렌더링함.

3. 상태 변경은 기존 값을 "복사"하여 수정

  • 상태는 불변성(immutability)을 유지해야 함
  • 객체나 배열을 직접 수정하지 말고, 스프레드 연산자(...)를 사용해 복사 후 변경
// 객체 상태
const [user, setUser] = useState({ name: '~~', age: 26 });

// ❌ 직접 수정 (잘못된 방식)
user.age = 25;
setUser(user); // 상태 변화 감지 실패할 수 있음

// ✅ 복사 후 변경
setUser({ ...user, age: 25 });

4. 상태가 많아지면 객체나 배열로 그룹화 가능

const [form, setForm] = useState({ name: '', age: '' });

const onChange = (e) => {
  const { name, value } = e.target;
  setForm({
    ...form,
    [name]: value, // name 속성에 따라 동적으로 갱신
  });
};

✅ 이 방식은 입력 필드가 많을 때 매우 유용하며 유지보수도 쉽다. 

5. 상태를 잘게 쪼개기보단 관련된 값은 묶어서 관리

좋지 않은 예 좋은 예
const [x, setX], const [y, setY] const [position, setPosition] = useState({ x: 0, y: 0 })

관련된 값은 하나의 객체로 묶어서 관리하는 것이 명확성과 유지보수에 유리함

 

✅ PDF 기반으로 다시 정리한 State 특징

번호 특징 설명
1 상태는 렌더링/데이터 흐름에 쓰일 때만 설정 UI에 영향을 주는 값만 state로 설정해야 함
2 렌더링과 무관한 데이터는 인스턴스 필드로 타이머 ID, DOM 참조 등은 state로 설정하지 않음 (useRef 등 사용)
3 컴포넌트 내부에서만 접근 가능 외부 컴포넌트에서 다른 컴포넌트의 state에 직접 접근은 불가능
4 리렌더링되어도 상태는 유지됨 함수형 컴포넌트라도 state는 React 내부에 저장됨
5 set함수를 통해서만 변경 가능 직접 변수처럼 수정하면 React가 감지하지 못함
6 set함수 사용 시 자동 리렌더링 새로운 값으로 상태가 변경되면 컴포넌트 다시 실행됨
7 렌더링 비용 고려 필요 불필요한 상태 선언은 성능 저하 유발할 수 있음

 

✅ 요약 정리 

항목 설명
선언 const [value, setValue] = useState(초기값)
변경 setValue(새로운값) 으로만 변경 가능
불변성 유지 객체/배열은 복사해서 수정해야 함
자동 렌더링 상태값이 바뀌면 컴포넌트가 자동으로 다시 렌더링됨
비동기 처리 상태 변경은 즉시 반영되는 게 아니라 React의 흐름에 따라 처리됨
실수 방지 상태는 반드시 컴포넌트 내부에서 선언, set 함수만 사용해서 변경해야 함

 


 이번 HTML과 CSS, 그리고 React까지 학습을 진행해보았는데, 개념을 듣고 읽으면서 이해해 보았다. 하지만 Toss 페이지 클론 코딩을 하면서 어떻게 동작하고, 코드의 구조가 어떻게 구성되는지를 이해한 것이 더욱 많았던 것 같기에, 추후 토스 클론 코딩 파트에 실습을 하며 궁금했던 점과 의문이 들었던 점들을 적어볼 예정이다. 

 

 이번에는 State까지 학습했고, 다음에는 컴포넌트에 대해서 자세히 알아보는 것을 시작으로 학습을 해볼 예정이다. 

 


본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.