Type something to search...

6 State 관리

1. State 관리

1.1. State란?

State는 컴포넌트의 데이터 저장소다. 값이 변경되면 화면이 자동으로 다시 그려진다.

1.1.1. 기본 사용법

import { useState } from 'react';

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

  return (
    <div>
      <p>횟수: {count}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </div>
  );
}

1.2. useState 동작 원리

useState는 두 개의 값을 배열로 반환한다.

  • 첫 번째: 현재 상태 값
  • 두 번째: 상태를 변경하는 함수
const [value, setValue] = useState(초기값);

1.2.1. 예제 1: 카운터

function Counter() {
  const [num, setNum] = useState(0);

  const plus = () => setNum(num + 1);
  const minus = () => setNum(num - 1);

  return (
    <div>
      <h2>{num}</h2>
      <button onClick={plus}>+</button>
      <button onClick={minus}>-</button>
    </div>
  );
}

1.3. 객체와 배열 State 관리

1.3.1. 객체 수정하기

주의

주의: 직접 수정 금지!

상태 객체나 배열을 직접 수정하면 안 된다. 반드시 새로운 객체/배열을 만들어야 한다.

const [user, setUser] = useState({ name: '철수', age: 20 });

// 전개 연산자 사용
setUser({ ...user, name: '영희' });
전개 연산자 미사용 (Object.assign)
  setUser(Object.assign({}, user, { name: '영희' }));

빈 객체()에 user를 복사하고, name을 ‘영희’로 덮어씌운 새 객체를 반환한다

전개 연산자(...)란?

...은 객체나 배열의 내용물을 꺼내서 펼치는 문법이다.

// 원본 객체
const user = { name: '철수', age: 20 };

// ...user는 name: '철수', age: 20을 꺼내서 펼친다
const newUser = { ...user, name: '영희' };
// 결과: { name: '영희', age: 20 }
// → 펼친 뒤 같은 키(name)를 덮어쓴다

배열도 같은 원리다:

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

// 1. 전개 연산자 사용
const newFruits = [...fruits, '오렌지'];
// 결과: ['사과', '바나나', '오렌지']
// → 기존 항목을 펼치고 새 항목을 추가한다

// 2. 전개 연산자 미사용 (concat 함수 사용)
const newFruits2 = fruits.concat('오렌지');
// 결과: ['사과', '바나나', '오렌지']

1.3.2. 배열 수정하기

const [items, setItems] = useState(['사과', '바나나']);

// ✅ 항목 추가
setItems([...items, '오렌지']);

// ✅ 항목 삭제 (filter 사용)
setItems(items.filter((item, index) => index !== 0));

1.4. useEffect란?

useEffect는 컴포넌트가 화면에 나타날 때, 사라질 때, 또는 특정 값이 변할 때 실행할 코드를 정의한다.

1.4.1. 기본 사용법

import { useEffect } from 'react';

useEffect(() => {
  console.log('화면이 나타남 (Mount)');

  return () => {
    console.log('화면에서 사라짐 (Unmount)');
  };
}, []); // 빈 배열: 처음 한 번만 실행

1.4.2. 의존성 배열

  • []: 처음 한 번만 실행
  • [count]: count 값이 변할 때마다 실행
  • 생략: 매번 렌더링될 때마다 실행

1.5. 실습: 간단한 Todo 앱

useState와 useEffect를 함께 활용하는 예제이다.

import { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');
  const [count, setCount] = useState(0);

  // useEffect: todos가 변할 때마다 남은 개수를 업데이트
  useEffect(() => {
    setCount(todos.filter(todo => !todo.done).length);
  }, [todos]);

  // 추가
  const addTodo = () => {
    if (!input) return;
    setTodos([...todos, { id: Date.now(), text: input, done: false }]);
    setInput('');
  };

  // 완료 토글
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ));
  };

  // 삭제
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <div className="todo-app">
      <h1>할 일 목록</h1>
      <p>남은 할 일: {count}개</p>

      <div className="todo-input">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="할 일을 입력하세요"
        />
        <button onClick={addTodo}>추가</button>
      </div>

      <ul className="todo-list">
        {todos.map(todo => (
          <li key={todo.id} className={todo.done ? 'done' : ''}>
            <span onClick={() => toggleTodo(todo.id)}>
              {todo.done ? '✅' : '⬜'} {todo.text}
            </span>
            <button onClick={() => deleteTodo(todo.id)}>삭제</button>
          </li>
        ))}
      </ul>

      {/* 조건부 렌더링: 할 일이 없으면 메시지 표시 */}
      {todos.length === 0 && <p>할 일을 추가해보세요!</p>}
    </div>
  );
}

export default App;

요약

  • useState: todos(배열), input(문자열), count(숫자) 3가지 상태 관리
  • useEffect: todos가 변할 때마다 남은 개수를 자동 계산
  • 전개 연산자: [...todos, 새항목]으로 배열에 추가
  • filter/map: 삭제와 수정에 활용
  • 조건부 렌더링: {todos.length === 0 && <p>메시지</p>}

1.6. 정리

  • State는 컴포넌트 내부에서 변하는 데이터를 관리한다
  • useState를 통해 상태와 상태 변경 함수를 얻는다
  • 상태가 변하면 컴포넌트는 다시 렌더링된다
  • useEffect로 생명주기(Lifecycle)를 관리한다
  • 객체나 배열 상태를 변경할 때는 불변성(Immutability)을 유지해야 한다