icon안동민 개발노트

React 상태 관리 기초


 React의 상태 관리는 동적이고 상호작용이 풍부한 UI를 구축하는 데 핵심적인 역할을 합니다. Next.js에서도 React의 상태 관리 기법을 그대로 활용할 수 있습니다. 이 절에서는 기본적인 상태 관리 방법과 Next.js에서의 적용에 대해 알아보겠습니다.

useState 훅 사용하기

 useState는 가장 기본적인 상태 관리 훅으로, 단순한 상태를 다룰 때 사용합니다.

'use client'
 
import { useState } from 'react'
 
export default function Counter() {
  const [count, setCount] = useState(0)
 
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

useReducer 훅 사용하기

 useReducer는 더 복잡한 상태 로직을 다룰 때 유용합니다.

'use client'
 
import { useReducer } from 'react'
 
function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 }
    case 'DECREMENT':
      return { count: state.count - 1 }
    default:
      throw new Error()
  }
}
 
export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 })
 
  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
    </div>
  )
}

상태 끌어올리기(Lifting State Up)

 여러 컴포넌트에서 동일한 상태를 공유해야 할 때, 상태를 공통 부모 컴포넌트로 끌어올립니다.

'use client'
 
import { useState } from 'react'
 
function TemperatureInput({ scale, temperature, onTemperatureChange }) {
  return (
    <fieldset>
      <legend>Enter temperature in {scale}:</legend>
      <input
        value={temperature}
        onChange={(e) => onTemperatureChange(e.target.value)}
      />
    </fieldset>
  )
}
 
export default function Calculator() {
  const [temperature, setTemperature] = useState('')
  const [scale, setScale] = useState('c')
 
  const handleCelsiusChange = (temperature) => {
    setScale('c')
    setTemperature(temperature)
  }
 
  const handleFahrenheitChange = (temperature) => {
    setScale('f')
    setTemperature(temperature)
  }
 
  const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature
  const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature
 
  return (
    <div>
      <TemperatureInput
        scale="c"
        temperature={celsius}
        onTemperatureChange={handleCelsiusChange}
      />
      <TemperatureInput
        scale="f"
        temperature={fahrenheit}
        onTemperatureChange={handleFahrenheitChange}
      />
    </div>
  )
}
 
// 변환 함수들은 여기에 구현

컴포넌트 간 상태 공유

 복잡한 애플리케이션에서는 Context API를 사용하여 깊은 레벨의 컴포넌트 간에 상태를 공유할 수 있습니다.

'use client'
 
import { createContext, useContext, useState } from 'react'
 
const ThemeContext = createContext()
 
export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light')
 
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}
 
export function useTheme() {
  return useContext(ThemeContext)
}
 
// 사용 예
function ThemedButton() {
  const { theme, setTheme } = useTheme()
  return (
    <button
      onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
      style={{ background: theme === 'light' ? '#fff' : '#000', color: theme === 'light' ? '#000' : '#fff' }}
    >
      Toggle Theme
    </button>
  )
}

7장 클라이언트 컴포넌트 사용법과의 연관성

 8장의 상태 관리 기법은 7장에서 다룬 클라이언트 컴포넌트와 밀접하게 연관됩니다. 상태 관리는 주로 클라이언트 컴포넌트에서 이루어지며, 'use client' 지시어를 사용하여 클라이언트 컴포넌트임을 명시해야 합니다. 서버 컴포넌트에서는 상태를 직접 관리할 수 없으므로, 상태가 필요한 부분은 클라이언트 컴포넌트로 분리하여 관리해야 합니다.

실습 : 할 일 목록(Todo list) 애플리케이션 구현

 다음 요구사항을 만족하는 간단한 할 일 목록 애플리케이션을 구현해보세요.

  1. 새로운 할 일 항목을 추가할 수 있는 입력 폼
  2. 할 일 목록 표시
  3. 각 할 일 항목을 완료 처리하거나 삭제할 수 있는 기능

 구현 예시

'use client'
 
import { useState } from 'react'
 
export default function TodoList() {
  const [todos, setTodos] = useState([])
  const [input, setInput] = useState('')
 
  const addTodo = (e) => {
    e.preventDefault()
    if (input.trim()) {
      setTodos([...todos, { id: Date.now(), text: input, completed: false }])
      setInput('')
    }
  }
 
  const toggleTodo = (id) => {
    setTodos(todos.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ))
  }
 
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id))
  }
 
  return (
    <div>
      <h1>Todo List</h1>
      <form onSubmit={addTodo}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Add a new todo"
        />
        <button type="submit">Add</button>
      </form>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span
              style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
              onClick={() => toggleTodo(todo.id)}
            >
              {todo.text}
            </span>
            <button onClick={() => deleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  )
}

 이 실습을 통해 useState를 사용한 상태 관리, 이벤트 처리, 그리고 상태 업데이트의 기본 패턴을 경험할 수 있습니다. 이는 실제 애플리케이션에서 자주 사용되는 패턴으로, React와 Next.js에서의 기본적인 상태 관리 기술을 이해하는 데 도움이 됩니다.