DEV/TypeScript

React에 TypeScript 적용하기 - 2. 이벤트 타입 적용하기

develop-ch94 2026. 8. 4. 10:25

1. React 이벤트 타입 기초

React는 브라우저의 네이티브 이벤트를 SyntheticEvent로 래핑하여 제공함
TypeScript에서는 각 이벤트별로 구체적인 타입을 제공함

 

주요 이벤트 타입들

import React from 'react';

// 1. MouseEvent - 마우스 관련 이벤트
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
  console.log('클릭됨:', e.button);
}
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
  console.log('클릭됨:', e.button);
}
// <button onClick={handleClick}>

// 2. FormEvent - 폼 관련 이벤트
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault();
  console.log('폼 제출됨');
}

// 3. ChangeEvent - 입력값 변경 이벤트
function handleChenge(e: React.ChangeEvent<HTMLInputElement>) {
  console.log('입력값:', e.target.value);
}
// <input onChange={handleChange} />

// 4. keyboardEvent - 키보드 이벤트
function handleKeyDown(e: React.keyboardEvent<HTMLInputElement>) {
  if (e.key === 'Enter') {
    console.log('엔터키 눌림');
  }
}

2. 이벤트 타입의 제네릭 활용

React의 이벤트 타입은 제네릭을 사용하여 이벤트가 발생한 Element의 타입을 지정할 수 있음
// 다양한 Element 타입 지정
function Component() {
  // 버튼 클릭
  const handleButtonClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    // e.currentTarget은 이벤트 핸들러가 등록된 요소(항상 HTMLButtonElement)
    console.log(e.currentTarget.disabled);
  };
  
  // div 클릭
  const handleDivClick = (e: React.MouseEvent<HTMLDivElement>) => {
    // e.currentTarget은 HTMLDivElement 타입
    console.log(e.currentTarget.className);
  };
  
  // input 변경
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    // e.target은 HTMLInputElement 타입
    console.log(e.target.value);
  };
  
  return (
    <div>
      <button onClick={handleButtonClick}>클릭</button>
      <div onClick={handleDivClick}>div 클릭</div>
      <input onChange={handleInputChange} />
    </div>
  );
}

3. EventHandler 타입 정의하기

재사용 가능한 이벤트 핸들러 타입을 정의할 수 있음
// 기본적인 EventHandler 타입들
type ButtonClickHandler = React.MouseEventHandler<HTMLButtonElement>;
type InputChangeHandler = React.ChangeEventHandler<HTMLInputElement>;
type FormSubmitHandler = React.FormEventHandler<HTMLFormElement>;
type KeyDownHandler = React.KeyboardEventHandler<HTMLInputElement>;

// 커스텀 EventHandler 타입 만들기
type CustomClickHandler = (
  event: React.MouseEvent<HTMLButtonElement>,
  data?: any
) => void;

type CustomInputHandler = (
  event: React.ChangeEvent<HTMLInputElement>,
  fieldName: string
) => void;

// 사용 예시
function MyComponent() {
  const handleClick: ButtonClickHandler = (e) => {
    console.log('버튼 클릭됨');
  };
  
  const handleCustomClick: CustomClickHandler = (e, data) => {
    console.log('커스텀 클릭:', data);
  };
  
  const handleInput: CustomInputHandler = (e, fieldName) => {
    console.log(`${fieldName} 필드값:', e.target.value);
  };
  
  return (
    <div>
      <button onClick={handleClick}>기본 클릭</button>
      <buttonn onClick={(e) => handleCustomClick(e, 'test')}>
        커스텀 클릭
      </button>
      <input
        onChange={(e) => handleInput(e, 'username')}
        placeholder="사용자명"
      />
    </div>
  );
}

4. e.target vs e.currentTarget 이해하기

JavaScript vs TypeScript 사용 패턴의 변화

  • JavaScript: e.target을 주로 사용(타입 검증 없이)
  • TypeScript: e.currentTarget을 더 선호(타입 안정성 때문에)

차이점

  • e.target: 실제로 이벤트가 발생한 element
  • e.currentTarget: 이벤트 리스너가 등록된 element
function EventPropagationExample() {
  const handleParentClick = (e: React.MouseEvent<HTMLDivElement>) => {
    console.log('target:', e.target); // 실제 클릭된 Element
    console.log('currentTarget:', e.currentTarget); // div Element
    
    // currentTarget은 항상 HTMLDivElement 타입이 보장됨
    console.log('부모 클래스:', e.currentTarget.className);
  };
  
  return (
    <div onClick={handleParentClick} className="parent">
      <button>버튼 1</button>
      <span>텍스트</span>
      <button>버튼 2</button>
    </div>
  );
}

 

타입 안정성 장점

function TypeSafetyExample() {
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
// ✅ currentTarget은 항상 HTMLButtonElement 타입
    e.currentTarget.disabled = true;// 타입 안전

// 🤔 Native DOM에서는 target, currentTarget 모두 EventTarget | null 타입
// React의 SyntheticEvent는 제네릭 타입(<HTMLButtonElement>)을 통해
// currentTarget의 타입을 정확하게 추론해주므로 타입 가드 불필요

// 리액트가 아닌 바닐라 자바스크립트 상황이면 target 사용시 타입 가드로 추가 검증(타입좁히기) 필요
//    if (e.target instanceof HTMLButtonElement) {
//      e.target.disabled = true;
//    }
//  };

  return <button onClick={handleClick}>클릭하세요</button>;
}

5. 실전 예제: 폼 관리

import React, { useState } from 'react';

interface FormData {
  username: string;
  email: string;
  password: string;
}

// 이벤트 핸들러 타입 정의
type InputHandler = React.ChangeEventHandler<HTMLInputElement>;
type SubmitHandler = React.FormEventHandler<HTMLFormElement>;
type ButtonClickHandler = React.MouseEventHandler<HTMLButtonElement>;

function LoginForm() {
  const [formData, setFormData] = useState<FormData>({
    username: '',
    email: '',
    password: ''
  });
  
  // 입력 필드 변경 처리
  const handleInputChange: InputHandler = (e) => {
    const { name, value } = e.currentTarget; // currentTarget 사용으로 타입 안전
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };
  
  // 폼 제출 처리
  const handleSubmit: SubmitHandler = (e) => {
    e.preventDefault();
    console.log('제출된 데이터:', formData);
  };
  
  // 리셋 버튼 처리
  const handleReset: ButtonClickHandler = (e) => {
    e.currentTarget.blur(); // currentTarget으로 안전하게 접근
    setFormData({ username: '', email: '', password: '' });
  };
  
  // 엔터키 처리
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter' && e.ctrlKey) {
      console.log('Ctrl + Enter 눌림');
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="username"
        value={formData.username}
        onChange={handleInputChange}
        onKeyDown={handleKeyDown}
        placeholder="사용자명"
      />
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleInputChange}
        placeholder="이메일"
      />
      <input
        type="password"
        name="password"
        value={formData.password}
        onChange={handleInputChange}
        placeholder="비밀번호"
      />
      <button type="submit">로그인</button>
      <button type="button" onClick={handleReset}>
        리셋
      </button>
    </form>
  );
}