5강. 이벤트 처리

사용자의 행동(클릭, 입력, 마우스 이동 등)에 반응하는 인터랙티브한 웹사이트를 만들기 위해 React의 이벤트 시스템을 깊이 있게 학습한다.

1. React 이벤트 시스템 이해하기

이벤트란?

이벤트(Event)는 사용자가 웹페이지에서 취하는 행동을 의미한다. 버튼 클릭, 키보드 입력, 마우스 이동 등 모든 상호작용이 이벤트다.

일상 생활의 비유:

도어벨을 누르면(이벤트) → 초인종이 울린다(반응)
리모컨 버튼을 누르면(이벤트) → TV 채널이 바뀐다(반응)
웹페이지에서 버튼을 클릭하면(이벤트) → 무언가 실행된다(반응)

React에서는 이런 "반응"을 이벤트 핸들러(Event Handler)라는 함수로 정의한다.

HTML vs React 이벤트 비교

구분 HTML React
이벤트명 소문자
onclick
카멜케이스
onClick
핸들러 문자열
"handleClick()"
함수
{handleClick}
기본 동작 방지 return false e.preventDefault()
// HTML 방식
<button onclick="handleClick()">클릭</button>

// React 방식
<button onClick={handleClick}>클릭</button>

주요 이벤트 종류

마우스 이벤트: 키보드 이벤트: 폼 이벤트:
⚠️ 가장 흔한 실수!

onClick={handleClick()} ❌ 잘못됨!
onClick={handleClick} ✅ 올바름!

이유: 괄호를 붙이면 함수가 즉시 실행된다.
function Button() {
  const handleClick = () => {
    console.log("클릭됨");
  };

  return (
    <div>
      {/* ❌ 렌더링 시 즉시 실행 */}
      <button onClick={handleClick()}>잘못된 예</button>

      {/* ✅ 클릭 시에만 실행 */}
      <button onClick={handleClick}>올바른 예</button>

      {/* ✅ 화살표 함수로 감싸면 OK */}
      <button onClick={() => handleClick()}>이것도 OK</button>
    </div>
  );
}

2. 클릭 이벤트

기본 클릭 이벤트

function ClickExample() {
  const handleClick = () => {
    alert("클릭됨");
  };

  const handleClick2 = () => {
    console.log("두 번째 버튼");
  };

  return (
    <div>
      <button onClick={handleClick}>버튼 1</button>
      <button onClick={handleClick2}>버튼 2</button>
      <button onClick={() => alert("버튼 3")}>버튼 3</button>
    </div>
  );
}

인자가 있는 함수 호출

function ButtonList() {
  const handleClick = (name) => {
    alert(`${name} 클릭됨`);
  };

  return (
    <div>
      {/* ❌ 잘못됨 */}
      <button onClick={handleClick("빨강")}>빨강</button>

      {/* ✅ 올바름 */}
      <button onClick={() => handleClick("빨강")}>빨강</button>
      <button onClick={() => handleClick("파랑")}>파랑</button>
    </div>
  );
}

다양한 이벤트 예시

function InteractiveButtons() {
  const handleDoubleClick = () => {
    alert("더블클릭");
  };

  const handleMouseEnter = () => {
    console.log("마우스 진입");
  };

  const handleContextMenu = (e) => {
    e.preventDefault();
    alert("우클릭 금지");
  };

  return (
    <div>
      <button onDoubleClick={handleDoubleClick}>더블클릭</button>
      <div onMouseEnter={handleMouseEnter} className="hover-box">
        마우스 올리기
      </div>
      <div onContextMenu={handleContextMenu}>우클릭 방지</div>
    </div>
  );
}

3. 키보드 이벤트

기본 키보드 이벤트

function KeyboardExample() {
  const handleKeyDown = (e) => {
    console.log(`눌린 키: ${e.key}`);
    console.log(`키코드: ${e.keyCode}`);
  };

  const handleEnter = (e) => {
    if (e.key === 'Enter') {
      alert("엔터 눌림");
    }
  };

  return (
    <div>
      <input
        type="text"
        onKeyDown={handleKeyDown}
        placeholder="키를 눌러보세요"
      />

      <input
        type="text"
        onKeyDown={handleEnter}
        placeholder="엔터를 눌러보세요"
      />
    </div>
  );
}

키 조합 감지

function KeyCombo() {
  const handleKeyDown = (e) => {
    if (e.ctrlKey && e.key === 's') {
      e.preventDefault();
      console.log("Ctrl+S 눌림");
    }

    if (e.shiftKey && e.key === 'Enter') {
      console.log("Shift+Enter 눌림");
    }
  };

  return (
    <textarea
      onKeyDown={handleKeyDown}
      placeholder="Ctrl+S 또는 Shift+Enter 눌러보기"
    />
  );
}

4. 폼 이벤트

onChange 이벤트

function InputExample() {
  const handleChange = (e) => {
    console.log("입력값:", e.target.value);
  };

  return (
    <div>
      <input
        type="text"
        onChange={handleChange}
        placeholder="입력하세요"
      />
    </div>
  );
}

onSubmit 이벤트

function FormExample() {
  const handleSubmit = (e) => {
    e.preventDefault();

    const formData = new FormData(e.target);
    const data = Object.fromEntries(formData);

    console.log("제출 데이터:", data);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="username" placeholder="이름" />
      <input type="email" name="email" placeholder="이메일" />
      <button type="submit">제출</button>
    </form>
  );
}

실시간 유효성 검사

function ValidationForm() {
  const handleUsernameChange = (e) => {
    const val = e.target.value;
    if (val.length < 3) {
      console.log("아이디는 3자 이상");
    }
  };

  const handleEmailChange = (e) => {
    const val = e.target.value;
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

    if (!regex.test(val)) {
      console.log("이메일 형식 오류");
    }
  };

  const handlePasswordChange = (e) => {
    const val = e.target.value;
    const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;

    if (!regex.test(val)) {
      console.log("비밀번호: 8자 이상, 문자+숫자+특수문자");
    }
  };

  return (
    <form>
      <input
        type="text"
        onChange={handleUsernameChange}
        placeholder="아이디"
      />
      <input
        type="email"
        onChange={handleEmailChange}
        placeholder="이메일"
      />
      <input
        type="password"
        onChange={handlePasswordChange}
        placeholder="비밀번호"
      />
    </form>
  );
}

Focus 이벤트

function FocusExample() {
  const handleFocus = (e) => {
    e.target.className = 'focused';
  };

  const handleBlur = (e) => {
    e.target.className = '';

    if (e.target.required && !e.target.value) {
      e.target.className = 'error';
    }
  };

  return (
    <div>
      <input
        type="text"
        onFocus={handleFocus}
        onBlur={handleBlur}
        placeholder="포커스 테스트"
      />
    </div>
  );
}

5. 종합 예제: 회원가입 폼

function SignupForm() {
  const handleSubmit = (e) => {
    e.preventDefault();

    const formData = new FormData(e.target);
    const data = Object.fromEntries(formData);

    console.log("회원가입 데이터:", data);
    alert("가입 완료");
  };

  const handleUsernameChange = (e) => {
    const val = e.target.value;
    if (val.length < 3 || val.length > 20) {
      console.log("아이디: 3-20자");
    }
  };

  const handleEmailChange = (e) => {
    const val = e.target.value;
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

    if (!regex.test(val)) {
      console.log("이메일 형식 오류");
    }
  };

  const handlePasswordChange = (e) => {
    const val = e.target.value;
    const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;

    if (!regex.test(val)) {
      console.log("비밀번호: 8자 이상, 문자+숫자+특수문자");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>아이디 *</label>
        <input
          type="text"
          name="username"
          required
          onChange={handleUsernameChange}
          placeholder="3-20자"
        />
      </div>

      <div>
        <label>이메일 *</label>
        <input
          type="email"
          name="email"
          required
          onChange={handleEmailChange}
          placeholder="example@mail.com"
        />
      </div>

      <div>
        <label>비밀번호 *</label>
        <input
          type="password"
          name="password"
          required
          onChange={handlePasswordChange}
          placeholder="8자 이상"
        />
      </div>

      <div>
        <label>나이</label>
        <input
          type="number"
          name="age"
          min="1"
          max="150"
        />
      </div>

      <div>
        <label>성별</label>
        <label>
          <input type="radio" name="gender" value="male" /> 남성
        </label>
        <label>
          <input type="radio" name="gender" value="female" /> 여성
        </label>
      </div>

      <div>
        <label>
          <input type="checkbox" name="agree" required />
          이용약관 동의 *
        </label>
      </div>

      <button type="submit">회원가입</button>
    </form>
  );
}

이벤트 처리 정리

이벤트 용도 주의사항
onClick 클릭 처리 함수 전달 시 괄호 금지
onChange 입력값 변경 실시간 발생
onSubmit 폼 제출 preventDefault 필수
onKeyDown 키보드 입력 e.key로 확인
onFocus/Blur 포커스 관리 유효성 검사 타이밍
다음 단계: State(상태) 학습

현재까지는 이벤트가 발생해도 화면이 업데이트되지 않는다. 다음 강의에서는 useState를 학습하여 화면을 동적으로 변경하는 방법을 배운다.