Learning
레슨 3 / 9·20분

State와 이벤트

State란?

State(상태)는 컴포넌트 내부에서 관리되는 동적 데이터입니다. Props가 외부에서 전달받는 읽기 전용 데이터라면, State는 컴포넌트 스스로 변경할 수 있는 데이터입니다. State가 변경되면 React는 해당 컴포넌트를 자동으로 다시 렌더링합니다.

useState 사용하기

tsx
import { useState } from 'react';

function Counter() {
  // useState는 [현재 값, 변경 함수] 배열을 반환
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(0)}>초기화</button>
    </div>
  );
}

이벤트 핸들링

React에서 이벤트는 camelCase로 작성합니다. HTML의 onclick은 React에서 onClick이 됩니다. 이벤트 핸들러에는 함수 자체를 전달해야 하며, 함수를 호출하면 안 됩니다(onClick={handleClick} O, onClick={handleClick()} X).

tsx
import { useState } from 'react';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();  // 폼 기본 제출 동작 방지
    console.log('로그인 시도:', { email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        이메일
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="이메일을 입력하세요"
        />
      </label>
      <label>
        비밀번호
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          placeholder="비밀번호를 입력하세요"
        />
      </label>
      <button type="submit">로그인</button>
    </form>
  );
}

State 불변성

React의 state는 직접 수정하면 안 되고, 항상 새로운 값을 만들어 setter 함수에 전달해야 합니다. 배열이나 객체의 경우 스프레드 연산자(...)를 활용하여 새 참조를 생성합니다. 이를 **불변성(immutability)**이라고 하며, React가 변경을 감지하여 화면을 다시 렌더링하기 위해 필수적입니다.

tsx
import { useState } from 'react';

function TodoList() {
  const [todos, setTodos] = useState<string[]>([]);
  const [input, setInput] = useState('');

  const addTodo = () => {
    if (!input.trim()) return;
    // 새 배열을 생성하여 전달 (불변성 유지)
    setTodos([...todos, input]);
    setInput('');
  };

  const removeTodo = (index: number) => {
    // filter로 새 배열 생성
    setTodos(todos.filter((_, i) => i !== index));
  };

  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={(e) => e.key === 'Enter' && addTodo()}
        placeholder="할 일 입력"
      />
      <button onClick={addTodo}>추가</button>
      <ul>
        {todos.map((todo, i) => (
          <li key={i}>
            {todo}
            <button onClick={() => removeTodo(i)}>삭제</button>
          </li>
        ))}
      </ul>
    </div>
  );
}
💡

todos.push(input) 처럼 기존 배열을 직접 수정하면 React가 변경을 감지하지 못해 화면이 업데이트되지 않습니다. 항상 setTodos([...todos, input])처럼 새 배열을 만들어야 합니다. 객체도 마찬가지로 setState({ ...prevState, key: newValue }) 패턴을 사용합니다.