WeniVooks

검색

React 베이스캠프

State란?

3. State란?

State는 컴포넌트의 상태를 나타내는 데이터입니다. Props와 달리 컴포넌트 내부에서 관리되며, 변경 가능하고 변경될 때마다 컴포넌트가 다시 렌더링됩니다.

3.1 Props vs State
Props (속성)
  • 부모 컴포넌트에서 전달받는 데이터
  • 읽기 전용 (변경 불가)
  • 컴포넌트 외부에서 제어
State (상태)
  • 컴포넌트 내부에서 관리하는 데이터
  • 변경 가능
  • 컴포넌트 내부에서 제어
  • 변경 시 컴포넌트 재렌더링
3.2 State가 필요한 경우
// State 없이 버튼 클릭 처리 (작동하지 않음)
function Counter() {
  let count = 0; // 일반 변수
 
  const handleClick = () => {
    count = count + 1; // 값은 변경되지만
    console.log(count); // 콘솔에는 찍히지만
    // 화면은 업데이트되지 않음!
  };
 
  return (
    <div>
      <h2>카운트: {count}</h2>
      <button onClick={handleClick}>+1</button>
    </div>
  );
}
// State 없이 버튼 클릭 처리 (작동하지 않음)
function Counter() {
  let count = 0; // 일반 변수
 
  const handleClick = () => {
    count = count + 1; // 값은 변경되지만
    console.log(count); // 콘솔에는 찍히지만
    // 화면은 업데이트되지 않음!
  };
 
  return (
    <div>
      <h2>카운트: {count}</h2>
      <button onClick={handleClick}>+1</button>
    </div>
  );
}

위의 예제에서 버튼을 클릭하면 콘솔에는 증가된 값이 출력되지만, 화면의 숫자는 변하지 않습니다. React는 일반 변수의 변경을 감지하지 못하기 때문입니다.

3.3 useState Hook 소개

React에서는 useState Hook을 사용해서 State를 관리합니다.

import { useState } from 'react';
 
function Counter() {
  // useState(초기값) -> [현재값, 값을변경하는함수]
  const [count, setCount] = useState(0);
 
  const handleClick = () => {
    setCount(count + 1); // State 값 변경
  };
 
  return (
    <div>
      <h2>카운트: {count}</h2>
      <button onClick={handleClick}>+1</button>
    </div>
  );
}
import { useState } from 'react';
 
function Counter() {
  // useState(초기값) -> [현재값, 값을변경하는함수]
  const [count, setCount] = useState(0);
 
  const handleClick = () => {
    setCount(count + 1); // State 값 변경
  };
 
  return (
    <div>
      <h2>카운트: {count}</h2>
      <button onClick={handleClick}>+1</button>
    </div>
  );
}
useState의 구조
const [상태값, 상태변경함수] = useState(초기값);
const [상태값, 상태변경함수] = useState(초기값);
  • 상태값: 현재 상태 데이터
  • 상태변경함수: 상태를 변경할 때 사용하는 함수
  • 초기값: 컴포넌트가 처음 렌더링될 때의 상태값
3.4 State 변경 시 주의사항
1. 직접 수정 금지
// ❌ 잘못된 방법 - 직접 수정
const [count, setCount] = useState(0);
count = count + 1; // 이렇게 하면 안됩니다!
 
// ✅ 올바른 방법 - setter 함수 사용
setCount(count + 1);
// ❌ 잘못된 방법 - 직접 수정
const [count, setCount] = useState(0);
count = count + 1; // 이렇게 하면 안됩니다!
 
// ✅ 올바른 방법 - setter 함수 사용
setCount(count + 1);
2. 비동기 특성 이해
function Counter() {
  const [count, setCount] = useState(0);
 
  const handleClick = () => {
    setCount(count + 1);
    console.log(count); // 아직 이전 값이 출력됨
  };
 
  // State 변경 후의 값을 확인하려면 useEffect 사용
  // (나중에 배울 예정)
}
function Counter() {
  const [count, setCount] = useState(0);
 
  const handleClick = () => {
    setCount(count + 1);
    console.log(count); // 아직 이전 값이 출력됨
  };
 
  // State 변경 후의 값을 확인하려면 useEffect 사용
  // (나중에 배울 예정)
}
3.5 다양한 타입의 State
1. 문자열 State
function NameInput() {
  const [name, setName] = useState('');
 
  return (
    <div>
      <input 
        type="text" 
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <p>안녕하세요, {name}님!</p>
    </div>
  );
}
function NameInput() {
  const [name, setName] = useState('');
 
  return (
    <div>
      <input 
        type="text" 
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <p>안녕하세요, {name}님!</p>
    </div>
  );
}
2. 불린 State
function ToggleButton() {
  const [isOn, setIsOn] = useState(false);
 
  return (
    <div>
      <button 
        onClick={() => setIsOn(!isOn)}
      >
        {isOn ? 'ON' : 'OFF'}
      </button>
      <p>상태: {isOn ? '켜짐' : '꺼짐'}</p>
    </div>
  );
}
function ToggleButton() {
  const [isOn, setIsOn] = useState(false);
 
  return (
    <div>
      <button 
        onClick={() => setIsOn(!isOn)}
      >
        {isOn ? 'ON' : 'OFF'}
      </button>
      <p>상태: {isOn ? '켜짐' : '꺼짐'}</p>
    </div>
  );
}
3. 배열 State
function TodoList() {
  const [todos, setTodos] = useState(['React 공부하기', 'JSX 이해하기']);
 
  const addTodo = () => {
    const newTodo = `할 일 ${todos.length + 1}`;
    setTodos([...todos, newTodo]); // 스프레드 연산자로 새 배열 생성
  };
 
  const removeTodo = (index) => {
    setTodos(todos.filter((_, i) => i !== index));
  };
 
  return (
    <div>
      <button onClick={addTodo}>할 일 추가</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            {todo}
            <button onClick={() => removeTodo(index)}>삭제</button>
          </li>
        ))}
      </ul>
    </div>
  );
}
function TodoList() {
  const [todos, setTodos] = useState(['React 공부하기', 'JSX 이해하기']);
 
  const addTodo = () => {
    const newTodo = `할 일 ${todos.length + 1}`;
    setTodos([...todos, newTodo]); // 스프레드 연산자로 새 배열 생성
  };
 
  const removeTodo = (index) => {
    setTodos(todos.filter((_, i) => i !== index));
  };
 
  return (
    <div>
      <button onClick={addTodo}>할 일 추가</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            {todo}
            <button onClick={() => removeTodo(index)}>삭제</button>
          </li>
        ))}
      </ul>
    </div>
  );
}
4. 객체 State
function UserProfile() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });
 
  const updateName = (newName) => {
    setUser({
      ...user, // 기존 객체 복사
      name: newName // 특정 속성만 업데이트
    });
  };
 
  const updateEmail = (newEmail) => {
    setUser(prevUser => ({
      ...prevUser,
      email: newEmail
    }));
  };
 
  return (
    <div>
      <input 
        type="text"
        placeholder="이름"
        value={user.name}
        onChange={(e) => updateName(e.target.value)}
      />
      <input 
        type="email"
        placeholder="이메일"
        value={user.email}
        onChange={(e) => updateEmail(e.target.value)}
      />
      <div>
        <h3>사용자 정보</h3>
        <p>이름: {user.name}</p>
        <p>이메일: {user.email}</p>
      </div>
    </div>
  );
}
function UserProfile() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });
 
  const updateName = (newName) => {
    setUser({
      ...user, // 기존 객체 복사
      name: newName // 특정 속성만 업데이트
    });
  };
 
  const updateEmail = (newEmail) => {
    setUser(prevUser => ({
      ...prevUser,
      email: newEmail
    }));
  };
 
  return (
    <div>
      <input 
        type="text"
        placeholder="이름"
        value={user.name}
        onChange={(e) => updateName(e.target.value)}
      />
      <input 
        type="email"
        placeholder="이메일"
        value={user.email}
        onChange={(e) => updateEmail(e.target.value)}
      />
      <div>
        <h3>사용자 정보</h3>
        <p>이름: {user.name}</p>
        <p>이메일: {user.email}</p>
      </div>
    </div>
  );
}
3.6 State 업데이트 최적화
함수형 업데이트

이전 상태값을 기반으로 새로운 값을 계산할 때 사용합니다:

function Counter() {
  const [count, setCount] = useState(0);
 
  // 일반적인 방법
  const increment = () => {
    setCount(count + 1);
  };
 
  // 함수형 업데이트 (권장)
  const incrementBetter = () => {
    setCount(prevCount => prevCount + 1);
  };
 
  // 여러 번 클릭할 때의 차이
  const incrementMultiple = () => {
    // 이렇게 하면 1만 증가 (count 값이 같기 때문)
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };
 
  const incrementMultipleBetter = () => {
    // 이렇게 하면 3 증가 (이전 값을 기반으로)
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
  };
 
  return (
    <div>
      <h2>카운트: {count}</h2>
      <button onClick={increment}>+1</button>
      <button onClick={incrementBetter}>+1 (Better)</button>
      <button onClick={incrementMultiple}>+3 (문제 있음)</button>
      <button onClick={incrementMultipleBetter}>+3 (올바름)</button>
    </div>
  );
}
function Counter() {
  const [count, setCount] = useState(0);
 
  // 일반적인 방법
  const increment = () => {
    setCount(count + 1);
  };
 
  // 함수형 업데이트 (권장)
  const incrementBetter = () => {
    setCount(prevCount => prevCount + 1);
  };
 
  // 여러 번 클릭할 때의 차이
  const incrementMultiple = () => {
    // 이렇게 하면 1만 증가 (count 값이 같기 때문)
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };
 
  const incrementMultipleBetter = () => {
    // 이렇게 하면 3 증가 (이전 값을 기반으로)
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
  };
 
  return (
    <div>
      <h2>카운트: {count}</h2>
      <button onClick={increment}>+1</button>
      <button onClick={incrementBetter}>+1 (Better)</button>
      <button onClick={incrementMultiple}>+3 (문제 있음)</button>
      <button onClick={incrementMultipleBetter}>+3 (올바름)</button>
    </div>
  );
}
3.7 실습: 간단한 쇼핑 카트

State를 활용한 쇼핑 카트를 만들어봅시다:

import { useState } from 'react';
 
function ShoppingCart() {
  const [items, setItems] = useState([]);
  const [inputValue, setInputValue] = useState('');
 
  const addItem = () => {
    if (inputValue.trim()) {
      const newItem = {
        id: Date.now(),
        name: inputValue.trim(),
        quantity: 1,
        price: Math.floor(Math.random() * 20000) + 1000
      };
      setItems([...items, newItem]);
      setInputValue('');
    }
  };
 
  const removeItem = (id) => {
    setItems(items.filter(item => item.id !== id));
  };
 
  const updateQuantity = (id, newQuantity) => {
    if (newQuantity <= 0) {
      removeItem(id);
      return;
    }
    
    setItems(items.map(item => 
      item.id === id ? { ...item, quantity: newQuantity } : item
    ));
  };
 
  const getTotalPrice = () => {
    return items.reduce((total, item) => total + (item.price * item.quantity), 0);
  };
 
  return (
    <div>
      <h1>쇼핑 카트</h1>
      
      <div>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="상품명을 입력하세요"
          onKeyPress={(e) => e.key === 'Enter' && addItem()}
        />
        <button onClick={addItem}>
          추가
        </button>
      </div>
 
      {items.length === 0 ? (
        <p>장바구니가 비어있습니다.</p>
      ) : (
        <>
          <div>
            {items.map(item => (
              <div key={item.id}>
                <div>
                  <strong>{item.name}</strong>
                  <br />
                  <small>{item.price.toLocaleString()}원</small>
                </div>
                
                <div>
                  <button 
                    onClick={() => updateQuantity(item.id, item.quantity - 1)}
                  >
                    -
                  </button>
                  <span>{item.quantity}</span>
                  <button 
                    onClick={() => updateQuantity(item.id, item.quantity + 1)}
                  >
                    +
                  </button>
                  <button 
                    onClick={() => removeItem(item.id)}
                  >
                    삭제
                  </button>
                </div>
              </div>
            ))}
          </div>
          
          <div>
            <h3>총 금액: {getTotalPrice().toLocaleString()}원</h3>
            <button>
              결제하기
            </button>
          </div>
        </>
      )}
    </div>
  );
}
 
export default ShoppingCart;
import { useState } from 'react';
 
function ShoppingCart() {
  const [items, setItems] = useState([]);
  const [inputValue, setInputValue] = useState('');
 
  const addItem = () => {
    if (inputValue.trim()) {
      const newItem = {
        id: Date.now(),
        name: inputValue.trim(),
        quantity: 1,
        price: Math.floor(Math.random() * 20000) + 1000
      };
      setItems([...items, newItem]);
      setInputValue('');
    }
  };
 
  const removeItem = (id) => {
    setItems(items.filter(item => item.id !== id));
  };
 
  const updateQuantity = (id, newQuantity) => {
    if (newQuantity <= 0) {
      removeItem(id);
      return;
    }
    
    setItems(items.map(item => 
      item.id === id ? { ...item, quantity: newQuantity } : item
    ));
  };
 
  const getTotalPrice = () => {
    return items.reduce((total, item) => total + (item.price * item.quantity), 0);
  };
 
  return (
    <div>
      <h1>쇼핑 카트</h1>
      
      <div>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="상품명을 입력하세요"
          onKeyPress={(e) => e.key === 'Enter' && addItem()}
        />
        <button onClick={addItem}>
          추가
        </button>
      </div>
 
      {items.length === 0 ? (
        <p>장바구니가 비어있습니다.</p>
      ) : (
        <>
          <div>
            {items.map(item => (
              <div key={item.id}>
                <div>
                  <strong>{item.name}</strong>
                  <br />
                  <small>{item.price.toLocaleString()}원</small>
                </div>
                
                <div>
                  <button 
                    onClick={() => updateQuantity(item.id, item.quantity - 1)}
                  >
                    -
                  </button>
                  <span>{item.quantity}</span>
                  <button 
                    onClick={() => updateQuantity(item.id, item.quantity + 1)}
                  >
                    +
                  </button>
                  <button 
                    onClick={() => removeItem(item.id)}
                  >
                    삭제
                  </button>
                </div>
              </div>
            ))}
          </div>
          
          <div>
            <h3>총 금액: {getTotalPrice().toLocaleString()}원</h3>
            <button>
              결제하기
            </button>
          </div>
        </>
      )}
    </div>
  );
}
 
export default ShoppingCart;
3.8 정리

State의 핵심 개념을 배웠습니다:

  1. State는 컴포넌트의 변경 가능한 데이터입니다
  2. useState Hook을 사용해서 State를 관리합니다
  3. State 변경은 setter 함수로만 가능합니다
  4. 다양한 데이터 타입을 State로 관리할 수 있습니다
  5. 함수형 업데이트로 더 안전하게 State를 변경할 수 있습니다

다음 장에서는 useState Hook에 대해 더 자세히 알아보겠습니다.

3장 State와 이벤트 처리3.2 useState Hook