React의 동적 측면 마스터하기: State, Events, and Conditional Rendering! (React Day 3)

발행: (2026년 1월 11일 오후 01:30 GMT+9)
14 min read
원문: Dev.to

I’m happy to translate the article for you, but I don’t have the article’s text in the message. Could you please paste the content you’d like translated (excluding any code blocks or URLs you want to keep unchanged)? Once I have the text, I’ll provide the Korean translation while preserving the original formatting and markdown.

상태 관리: 로컬 데이터를 위한 useState 소개

State는 React가 시간에 따라 변하는 데이터를 기억하는 방법입니다—예를 들어 카운터 값이나 폼 입력값처럼. 부모 컴포넌트에서 전달되는 props와 달리, state는 컴포넌트에 프라이빗하게 존재하며 업데이트될 때마다 리렌더링을 트리거합니다.

useState는 훅(후에 훅에 대해 더 자세히 다룹니다)으로, 상태 변수와 그 값을 업데이트하는 setter 함수를 제공합니다. React에서 다음과 같이 임포트합니다:

import { useState } from 'react';

작동 방식

const [value, setValue] = useState(initialValue);
  • value는 읽기 전용입니다.
  • 값을 변경하려면 setValue를 호출합니다.

실시간 예제: 간단한 카운터

import { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

버튼을 클릭하면 → state가 업데이트되고 → 컴포넌트가 새로운 카운트와 함께 리렌더링됩니다.

실제 예제: 입력값을 추적하는 로그인 폼

import { useState } from 'react';

function LoginForm() {
  const [username, setUsername] = useState('');

  return (
    <input
      type="text"
      value={username}
      onChange={e => setUsername(e.target.value)}
      placeholder="Username"
    />
  );
}

State가 입력값을 동기화해 주어, 제출 준비가 언제든 가능하도록 합니다.

흔히 저지르는 실수

  • 직접 변이count++와 같이 직접 값을 증가시켜도 리렌더링이 일어나지 않습니다. 반드시 setter를 사용하세요.
  • setter 생략setValue 없이 변수를 업데이트하면 아무 일도 일어나지 않습니다.

디버깅 팁

console.log('Current count:', count);

컴포넌트 내부에 로그를 배치하면 각 렌더링 시점에 현재 값을 확인할 수 있습니다. 리렌더링은 비동기적으로 발생한다는 점을 기억하세요.

이벤트 처리: 사용자 행동에 응답하기

React의 이벤트는 HTML처럼 보이지만 camelCase(예: onClick)를 사용하고 함수 레퍼런스를 받습니다.

비유: 초인종에 리스너를 붙이는 것—사용자가 “울리면”(클릭하면) 코드가 반응합니다.

실시간 예제 (Counter에서)

<button onClick={() => setCount(count + 1)}>Increment</button>

폼 이벤트

  • onChange – 입력 변화 감지.
  • onSubmit – 폼 제출 처리.

실제 예제: Todo 앱에서 아이템 추가하기

import { useState } from 'react';

function TodoForm({ addTodo }) {
  const [text, setText] = useState('');

  const handleSubmit = e => {
    e.preventDefault();          // 페이지 새로고침 방지
    addTodo(text);
    setText('');                 // 입력란 비우기
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={e => setText(e.target.value)}
        placeholder="New todo"
      />
      <button type="submit">Add</button>
    </form>
  );
}

흔히 하는 실수

  • 폼에서 e.preventDefault()를 빼먹음 → 페이지가 새로고침됩니다.
  • 매 렌더마다 무거운 인라인 함수를 정의함 → useCallback 사용이나 함수를 별도로 추출하는 것을 고려하세요.

디버깅 팁

React DevTools를 열고, 컴포넌트의 props와 state를 확인한 뒤 이벤트 객체를 로그하세요:

const handleClick = e => {
  console.log(e);
  // …your logic
};

조건부 렌더링: 표시/숨기기 기반 로직

조건에 따라 다른 UI를 렌더링합니다—일반 JavaScript의 if/else와 동일합니다.

방법

TechniqueSyntax
삼항 연산자{condition ? <A /> : <B />}
단축 평가{condition && <A />}
조기 반환if (!condition) return null;

실시간 예제: 토글 가시성

import { useState } from 'react';

function Toggle() {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <div>
      <button onClick={() => setIsVisible(!isVisible)}>Toggle</button>
      {isVisible && <p>Now you see me!</p>}
    </div>
  );
}

실제 예제: 로딩 스피너

import { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(json => {
        setData(json);
        setLoading(false);
      });
  }, []);

  return loading ? (
    <p>Loading...</p>
  ) : (
    <pre>{JSON.stringify(data)}</pre>
  );
}

흔히 하는 실수

  • if 문을 JSX 반환 바깥에 배치하는 경우—JSX는 표현식이므로 삼항 연산자나 단축 평가를 내부에서 사용하세요.
  • 깊게 중첩된 조건문 → 가독성을 위해 별도 컴포넌트로 추출합니다.

동적 UI 업데이트: 모든 것을 하나로

상태, 이벤트, 그리고 조건문을 결합하여 사용자에게 반응하는 UI를 만들기—예를 들어, 입력할 때마다 업데이트되는 필터 가능한 리스트.

실제 예시: 검색 폼

import { useState } from 'react';

function SearchList() {
  const [query, setQuery] = useState('');
  const items = ['Apple', 'Banana', 'Cherry'];

  const filtered = items.filter(item =>
    item.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search fruits"
      />
      <ul>
        {filtered.length ? (
          filtered.map(item => <li key={item}>- {item}</li>)
        ) : (
          <li>No results</li>
        )}
      </ul>
    </div>
  );
}

입력onChangequery를 업데이트 → filtered가 다시 계산 → JSX가 일치하는 리스트를 다시 렌더링.

Source:

React가 컴포넌트를 다시 렌더링하는 방법

state 또는 props가 변경되면 React는:

  1. 컴포넌트 함수를 다시 실행합니다.
  2. 새로운 가상 DOM 트리를 생성합니다.
  3. 새 트리를 이전 트리와 비교합니다.
  4. 실제 DOM에 필요한 변경 사항만 적용합니다.

비유

스케치를 다시 그리는 것과 같습니다: 변경된 부분만 지우고 다시 그립니다.

렌더 사이클 시각화

React render cycle diagram

다이어그램은 state/prop 변경 → render → commit → 브라우저 페인팅 흐름을 보여줍니다.

핵심 포인트

  • 불변성 – 상태는 항상 불변으로 다루세요; React는 참조 변화로 업데이트를 감지합니다.
  • 배칭 – 같은 이벤트 루프 내에서 여러 상태 업데이트가 하나의 렌더링으로 배치되어 성능이 향상됩니다.
  • 메모이제이션React.memo, useMemo, useCallback을 사용해 불필요한 재렌더링을 방지하세요.

디버깅 팁

React DevTools에서 “Highlight updates” 옵션을 활성화하면 상태 변경 후 어떤 컴포넌트가 다시 렌더링되는지 확인할 수 있습니다. 여기에 console.log 문을 추가하면 렌더 흐름을 추적할 수 있습니다.

🎉 준비 완료!

이제 React 컴포넌트를 상태가 있는, 인터랙티브한, 그리고 동적인으로 만들 수 있는 핵심 도구들을 갖추었습니다. 예제들을 실험해보고, 문제를 일으킨 뒤 다시 고쳐보세요—그 과정에서 배움이 이루어집니다. 즐거운 코딩 되세요!

상태 및 라이프사이클 개요

State and LifeCycle in React – Beginner's Guide

디버깅 팁: React DevTools Profiler를 사용하여 렌더링을 기록하고 불필요한 렌더링을 찾아보세요.

State Immutability – Why You Can’t Mutate Directly

State는 읽기 전용으로 다루어야 합니다. 상태를 변형하면(e.g., array.push(item)) React에 알리지 않으므로 UI가 업데이트되지 않습니다.

  • 왜? React는 변경을 감지하기 위해 참조 동일성을 확인합니다. 변형은 동일한 참조를 유지하므로 React는 재렌더링을 건너뜁니다.
  • 베스트 프랙티스: 상태를 업데이트할 때는 새로운 객체/배열을 생성하세요.
// ❌ Mutating the original array
array.push(item);
setArray(array);

// ✅ Creating a new array
setArray([...array, item]);

The React State Bug That Doesn't Throw Errors (But Breaks Your UI)

흔한 실수: 중첩된 객체를 변형하는 것입니다. 깊은 복사 또는 Immer와 같은 라이브러리를 사용하세요.

배치 – 효율성을 위한 업데이트 그룹화

React는 이벤트 핸들러나 라이프사이클 메서드 내부에서 특히 여러 상태 업데이트를 자동으로 배치하여 하나의 재렌더링으로 처리합니다.

// Both updates are batched → count increases by 2 in one render
setCount(c => c + 1);
setCount(c => c + 1);
  • 왜? DOM 과다 업데이트를 줄이고 성능을 향상시킵니다.
  • 주의점: 비동기 코드에서는 업데이트가 즉시 적용되지 않습니다. 상태가 변경된 후에 동작이 필요하면 콜백이나 useEffect를 사용하세요.

Props vs. State – 핵심 비교

측면PropsState
Source부모 컴포넌트에서 아래로 전달됨컴포넌트 내부에서 관리
Mutability읽기 전용 – 자식이 변경할 수 없음변경 가능 – setter 함수(setX)를 통해
TriggerProps가 변경되면 자식이 다시 렌더링됨State가 변경되면 컴포넌트와 그 자식들이 다시 렌더링됨
Analogy부모에게 받은 선물 – 변경할 수 없음자신의 지갑 – 사용하거나 추가할 수 있음

What is State and Props?

  • Props를 사용할 때: 설정 데이터, 부모로부터 전달되는 값, 혹은 자식 내부에서 불변이어야 하는 모든 것.
  • State를 사용할 때: 사용자 상호작용, 네트워크 응답, 타이머 등으로 시간에 따라 변하는 데이터.
  • 흔한 실수: 파생 데이터를 state에 저장하는 것. 대신 props/state에서 즉시 계산하여 동기화 버그를 방지하세요.

디버깅 팁: UI가 업데이트되지 않을 경우, render 함수 안에서 props와 state를 모두 로그에 출력해 기대값이 들어있는지 확인하세요.

마무리 – React 앱이 살아나다

이제 다음을 마스터했습니다:

  • State – 불변 업데이트, 배칭, 디버깅.
  • Props – 부모에서 자식으로의 단방향 데이터 흐름.
  • Lifecycle basics – 컴포넌트가 마운트, 업데이트, 언마운트되는 시점.

이러한 개념은 카운터, 폼, 대시보드, 채팅 인터페이스와 같은 실제 기능을 구현합니다.

다음 단계: Day 4 – Lists, Keys, and Effects.

연습 아이디어: 사용자가 항목을 추가, 편집, 삭제할 수 있는 동적 todo list를 만들어 보세요. 다음을 기억하세요:

  1. 원본 리스트를 절대 변형하지 말고; 항상 새로운 배열을 생성하세요.
  2. 배칭을 활용하여 여러 상태 변경이 이전 상태에 의존할 때 함수형 업데이트를 사용하세요.
  3. 키를 올바르게 사용하여 리스트 항목이 안정적으로 유지되도록 하세요.

즐거운 코딩 되세요, 그리고 계속 React 하세요! 🚀

Back to Blog

관련 글

더 보기 »

ReactJS Hook 패턴 ~Latest Ref 패턴~

ReactJS Hook Pattern ~Latest Ref Pattern~의 커버 이미지 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%...