[REACT] 상태 관리: useState로 컴포넌트의 동적 메모리 활용하기

상태가 필요한 이유

사용자가 버튼을 누르거나 입력 필드에 텍스트를 추가할 때, 컴포넌트는 그 변화를 기억하고 UI에 반영해야 합니다. 예를 들어, 체크리스트에서 항목을 선택하거나, 탭 메뉴에서 활성 탭을 전환할 때, 단순 변수로는 이를 유지할 수 없습니다. 상태(state)는 이런 데이터를 저장하고, 변경 시 컴포넌트를 자동으로 업데이트하는 역할을 합니다.

useState로 상태 선언하기

useState는 상태 변수와 그 값을 업데이트하는 함수를 쌍으로 반환합니다. 상태가 변경되면 React는 컴포넌트를 재렌더링해 최신 상태를 반영합니다.

import { useState } from 'react';

export default function TogglePanel() {
  const [isOpen, setIsOpen] = useState(false);

  function handleToggle() {
    setIsOpen(!isOpen);
  }

  return (
    <div style={{ padding: '15px', border: '1px solid #ddd', borderRadius: '5px' }}>
      <button 
        onClick={handleToggle}
        style={{ marginBottom: '10px', padding: '5px 10px' }}
      >
        {isOpen ? '패널 닫기' : '패널 열기'}
      </button>
      {isOpen && (
        <div style={{ backgroundColor: '#f9f9f9', padding: '10px' }}>
          <h3>숨겨진 콘텐츠</h3>
          <p>이 내용은 패널이 열려 있을 때만 표시됩니다.</p>
        </div>
      )}
    </div>
  );
}
  • useState(false): isOpen의 초기값을 false로 설정합니다.
  • setIsOpen: 이 함수는 isOpen를 업데이트하며, 새로운 값이 설정되면 컴포넌트를 다시 렌더링합니다.

다중 상태 변수 활용

하나의 컴포넌트에서 여러 가지 데이터를 기억할 필요가 있을 때, useState를 여러 번 호출할 수 있습니다. 상태가 서로 관련이 없을 경우, 각각 분리하여 관리하는 것이 더 명확하고 유지보수가 용이합니다.

import { useState } from 'react';

export default function TaskTracker() {
  const [tasksCompleted, setTasksCompleted] = useState(0);
  const [totalTasks, setTotalTasks] = useState(3);

  function handleCompleteTask() {
    if (tasksCompleted < totalTasks) {
      setTasksCompleted(tasksCompleted + 1);
    }
  }

  function handleAddTask() {
    setTotalTasks(totalTasks + 1);
  }

  return (
    <div style={{ padding: '20px', border: '1px solid #eee', maxWidth: '400px' }}>
      <h2>작업 추적기</h2>
      <p>완료된 작업: {tasksCompleted} / 총 작업: {totalTasks}</p>
      <button 
        onClick={handleCompleteTask}
        style={{ marginRight: '10px', padding: '5px 10px' }}
      >
        작업 완료
      </button>
      <button 
        onClick={handleAddTask}
        style={{ padding: '5px 10px' }}
      >
        작업 추가
      </button>
    </div>
  );
}

컴포넌트에 국한된 메모리

상태는 해당 컴포넌트 인스턴스에만 존재하며, 동일한 컴포넌트를 여러 번 렌더링하면 각 인스턴스는 독립적인 상태를 갖게 됩니다. 이는 상태가 외부와 격리되어 데이터 충돌을 방지한다는 뜻입니다.

import TaskTracker from './TaskTracker';

export default function App() {
  return (
    <div style={{ display: 'flex', gap: '20px', padding: '20px' }}>
      <TaskTracker />
      <TaskTracker />
    </div>
  );
}
  • 두 개의 TaskTracker 컴포넌트는 각자의 tasksCompleted와 totalTasks를 관리합니다.
  • 한 인스턴스의 상태 변경이 다른 인스턴스에 영향을 주지 않습니다.
  • 공유가 필요할 경우 상태를 부모로 올려 props로 전달해야 합니다.

상태와 로컬 변수의 차이

  • 로컬 변수는 함수 호출 간 유지되지 않고, 변경 시 재렌더링을 유발하지 않습니다. 
  • 상태는 React가 관리하며, 값이 바뀌면 UI를 새로 그립니다.

로컬 변수

export default function WrongExample() {
  let count = 0; // 로컬 변수

  function handleClick() {
    count += 1; // 값은 변하지만 UI는 갱신되지 않음
    console.log(count);
  }

  return <button onClick={handleClick}>클릭: {count}</button>;
}

useState로 상태 관리

export default function CorrectExample() {
  const [count, setCount] = useState(0);

  return <button onClick={() => setCount(count + 1)}>클릭: {count}</button>;
}

상태 관리 주의 사항

  • 최소화: 불필요한 상태 선언을 피하고, 파생된 값은 계산으로 처리합니다.
  • 객체 사용: 관련된 데이터를 하나의 상태로 묶어서 관리합니다.
  • 상태 업데이트가 잦으면 useReducer 또는 메모이젱션을 고려합니다.