리액트 state 관리: useState 기본 사용법과 동작 원리

리액트 state 관리: useState 기본 사용법과 동작 원리

리액트(React)는 컴포넌트의 동적인 데이터를 다루기 위해 state 개념을 제공합니다. state는 UI와 데이터의 일관성을 유지하는 핵심 도구이며, 함수형 컴포넌트에서는 useState 훅을 사용해 관리합니다. 이 글에서는 state 변경 규칙(불변성)과 함께, 카운터 예제, 입력 폼 예제를 통해 useState의 기본 원리를 알아봅니다.

1. useState 기본 사용법

useState는 배열을 반환하며, 첫 번째 값은 현재 상태(state), 두 번째 값은 상태를 업데이트하는 함수(setter)입니다.

import React, { useState } from 'react';

export default function CounterBasic() {
  const [count, setCount] = useState(0); // 초기값 0

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

주의: setter 함수(setCount)를 직접 호출해야 UI가 다시 렌더링됩니다. count++ 같은 방식으로 직접 변경하면 동작하지 않습니다.

2. state 변경 규칙 (불변성)

리액트 state는 불변성(immutability)을 유지해야 합니다. 기존 값을 직접 수정하는 것이 아니라, 새로운 값을 만들어 setState 함수로 전달해야 합니다.

import React, { useState } from 'react';

export default function TodoList() {
  const [todos, setTodos] = useState(['공부하기', '운동하기']);

  const addTodo = () => {
    // 잘못된 방법 ❌ (원본 배열 수정)
    // todos.push('독서하기');
    // setTodos(todos);

    // 올바른 방법 ✅ (새 배열 생성)
    setTodos([...todos, '독서하기']);
  };

  return (
    <div>
      <h3>할 일 목록</h3>
      <ul>
        {todos.map((todo, idx) => (
          <li key={idx}>{todo}</li>
        ))}
      </ul>
      <button onClick={addTodo}>할 일 추가</button>
    </div>
  );
}

✅ 배열/객체 state는 spread 연산자(...)map, filter 등을 활용해 새로운 값을 생성하는 방식으로 갱신해야 합니다.

3. 입력 폼 예제

폼 입력값을 관리할 때도 state를 활용합니다. 입력 이벤트(onChange)를 감지하여 상태를 업데이트하고, 그 값이 즉시 UI에 반영됩니다.

import React, { useState } from 'react';

export default function FormExample() {
  const [name, setName] = useState('');
  const [age, setAge] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`이름: ${name}, 나이: ${age}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>이름: </label>
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </div>
      <div>
        <label>나이: </label>
        <input
          type="number"
          value={age}
          onChange={(e) => setAge(e.target.value)}
        />
      </div>
      <button type="submit">제출</button>
    </form>
  );
}

✅ 입력 폼 예제는 state가 UI와 데이터의 양방향 연결을 어떻게 구현하는지 보여줍니다. React에서는 이런 패턴을 Controlled Component라고 부릅니다.

4. 통합 예제

아래 코드는 카운터 + 할 일 목록 + 입력 폼을 한 화면에서 확인할 수 있도록 만든 통합 예제입니다.

import React, { useState } from 'react';

export default function StateDemo() {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState(['React 공부하기']);
  const [input, setInput] = useState('');

  const addTodo = () => {
    if (!input) return;
    setTodos([...todos, input]);
    setInput('');
  };

  return (
    <div style={{ padding: '16px' }}>
      <h2>state 통합 예제</h2>

      {/* 카운터 */}
      <div>
        <p>카운트: {count}</p>
        <button onClick={() => setCount(count + 1)}>+1</button>
        <button onClick={() => setCount(count - 1)}>-1</button>
      </div>

      {/* 할 일 목록 */}
      <div>
        <h3>할 일 목록</h3>
        <ul>
          {todos.map((todo, idx) => (
            <li key={idx}>{todo}</li>
          ))}
        </ul>
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
        />
        <button onClick={addTodo}>추가</button>
      </div>
    </div>
  );
}

▼ 실행 결과 화면

리액트 state 예제 코드 실행 결과 화면

마무리

state는 리액트 컴포넌트의 동적 데이터를 관리하는 핵심 도구입니다. useState 훅으로 간단히 정의할 수 있으며, 반드시 setter 함수를 통해 값을 변경해야 합니다. 또한 배열/객체 상태는 불변성을 지켜 새로운 값을 만들어 갱신해야 합니다. 이를 숙지하면 카운터, 입력 폼, 리스트 같은 기본 기능을 쉽게 구현할 수 있습니다.

참고 링크

함께 보면 좋은 게시글

위로 스크롤