React 입문 8편 - 이벤트 처리하기

(수정: ) learning by Seven Fingers Studio 16분
ReactEventonClickonChange이벤트처리

react event handling 2025

이벤트 핸들러 함수명 짓는 거 고민 많이 했어요. handle로 시작할지 on으로 시작할지. 나중에 보니까 handle은 실제 함수, on은 Props로 넘길 때 쓰는 게 컨벤션이더라고요. 그거 알고 나서 코드 읽기가 훨씬 편해졌어요.

웹사이트에서 버튼 클릭하면 뭔가 동작하잖아요. 입력창에 글 쓰면 실시간으로 반응하고요. 이런 걸 이벤트 처리라고 해요. React에서 이벤트 다루는 법을 알아볼게요.

HTML vs React 이벤트 차이점

HTML에서 이벤트 처리하던 방식이랑 약간 달라요.

<!-- HTML 방식 -->
<button onclick="handleClick()">클릭</button>
// React 방식
<button onClick={handleClick}>클릭</button>

차이점 3가지:

  1. 이벤트명이 카멜케이스 (onclick → onClick)
  2. 함수를 문자열이 아닌 중괄호로 전달
  3. 함수를 호출하지 않고 전달 (handleClick() ❌ → handleClick ⭕)

onClick - 클릭 이벤트

가장 많이 쓰는 이벤트예요.

기본 사용법

function App() {
  const handleClick = () => {
    alert("버튼을 클릭했습니다!");
  };

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

인라인으로 쓰기

간단한 동작은 바로 써도 돼요:

<button onClick={() => alert("클릭!")}>클릭</button>

주의: 함수 호출하면 안 됨

// ❌ 잘못된 방법 - 렌더링할 때 바로 실행됨!
<button onClick={handleClick()}>클릭</button>

// ⭕ 올바른 방법
<button onClick={handleClick}>클릭</button>

handleClick()처럼 괄호를 붙이면 컴포넌트가 렌더링될 때 바로 실행돼버려요.

매개변수 전달하기

함수에 값을 넘기고 싶으면 화살표 함수로 감싸세요:

function App() {
  const handleClick = (name) => {
    alert(`${name}님, 안녕하세요!`);
  };

  return (
    <div>
      <button onClick={() => handleClick("홍길동")}>홍길동</button>
      <button onClick={() => handleClick("김철수")}>김철수</button>
    </div>
  );
}

이벤트 객체 (event)

이벤트 핸들러는 자동으로 이벤트 객체를 받아요:

function App() {
  const handleClick = (e) => {
    console.log(e);           // 이벤트 객체
    console.log(e.target);    // 클릭된 요소
    console.log(e.type);      // 이벤트 타입 ("click")
  };

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

매개변수와 이벤트 객체 둘 다 받기

const handleClick = (id, e) => {
  console.log("ID:", id);
  console.log("이벤트:", e);
};

<button onClick={(e) => handleClick(123, e)}>클릭</button>

onChange - 입력 이벤트

입력창에서 값이 바뀔 때 발생해요:

function App() {
  const handleChange = (e) => {
    console.log(e.target.value);  // 입력된 값
  };

  return <input type="text" onChange={handleChange} />;
}

실시간 입력 표시

import { useState } from 'react';

function App() {
  const [text, setText] = useState("");

  const handleChange = (e) => {
    setText(e.target.value);
  };

  return (
    <div>
      <input type="text" onChange={handleChange} />
      <p>입력값: {text}</p>
    </div>
  );
}

입력할 때마다 바로 밑에 보여요!

onSubmit - 폼 제출

폼 제출할 때 발생하는 이벤트예요:

function LoginForm() {
  const handleSubmit = (e) => {
    e.preventDefault();  // 페이지 새로고침 방지!
    console.log("폼 제출됨");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" placeholder="아이디" />
      <input type="password" placeholder="비밀번호" />
      <button type="submit">로그인</button>
    </form>
  );
}

e.preventDefault()가 중요해요! 안 쓰면 폼 제출하자마자 페이지가 새로고침돼요.

자주 쓰는 이벤트 목록

마우스 이벤트

<button onClick={handleClick}>클릭</button>
<button onDoubleClick={handleDoubleClick}>더블클릭</button>
<div onMouseEnter={handleEnter}>마우스 올리면</div>
<div onMouseLeave={handleLeave}>마우스 나가면</div>
<div onMouseMove={handleMove}>마우스 움직이면</div>

키보드 이벤트

<input onKeyDown={handleKeyDown} />   // 키 누를 때
<input onKeyUp={handleKeyUp} />       // 키 뗄 때
<input onKeyPress={handleKeyPress} /> // 글자 입력될 때 (deprecated)

react event handling 2025

function App() {
  const handleKeyDown = (e) => {
    if (e.key === "Enter") {
      console.log("엔터 눌림!");
    }
    if (e.key === "Escape") {
      console.log("ESC 눌림!");

    }
  };

  return <input onKeyDown={handleKeyDown} />;
}

포커스 이벤트

<input onFocus={handleFocus} />  // 포커스 받을 때
<input onBlur={handleBlur} />    // 포커스 잃을 때
function App() {
  const handleFocus = () => {
    console.log("입력창 선택됨");
  };

  const handleBlur = () => {
    console.log("입력창에서 나감");
  };

  return (
    <input
      onFocus={handleFocus}
      onBlur={handleBlur}
      placeholder="클릭해보세요"
    />
  );
}

실전 예제: 카운터 앱

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const increase = () => setCount(count + 1);
  const decrease = () => setCount(count - 1);
  const reset = () => setCount(0);

  return (
    <div style={{ textAlign: 'center', padding: '20px' }}>
      <h1>{count}</h1>
      <button onClick={decrease}>-</button>
      <button onClick={reset}>리셋</button>
      <button onClick={increase}>+</button>
    </div>
  );
}

실행 결과:

7

버튼 클릭 이벤트로 카운터를 조작할 수 있습니다

실전 예제: 검색창

import { useState } from 'react';

function SearchBox() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);

  const fruits = ["사과", "바나나", "오렌지", "포도", "수박", "딸기"];

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);

    if (value.trim()) {
      const filtered = fruits.filter((fruit) =>
        fruit.includes(value)
      );
      setResults(filtered);
    } else {
      setResults([]);
    }
  };

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={handleChange}
        placeholder="과일 검색..."
        style={{ padding: '10px', width: '200px' }}
      />
      <ul>
        {results.map((fruit) => (
          <li key={fruit}>{fruit}</li>
        ))}
      </ul>
    </div>
  );
}

실시간으로 검색 결과가 필터링돼요!

실전 예제: 할 일 추가하기

import { useState } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!input.trim()) return;

    setTodos([...todos, { id: Date.now(), text: input }]);
    setInput("");  // 입력창 비우기
  };

  const handleDelete = (id) => {
    setTodos(todos.filter((todo) => todo.id !== id));
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="할 일 입력..."
        />
        <button type="submit">추가</button>
      </form>

      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            {todo.text}
            <button onClick={() => handleDelete(todo.id)}>삭제</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

이벤트 버블링과 stopPropagation

클릭 이벤트는 부모까지 전파돼요(버블링):

function App() {
  const handleParent = () => console.log("부모 클릭");
  const handleChild = () => console.log("자식 클릭");

  return (
    <div onClick={handleParent} style={{ padding: '50px', background: '#ddd' }}>
      <button onClick={handleChild}>클릭</button>
    </div>
  );
}
// 버튼 클릭 시: "자식 클릭" -> "부모 클릭" 둘 다 출력됨

전파를 막으려면:

const handleChild = (e) => {
  e.stopPropagation();  // 부모로 이벤트 전파 막기
  console.log("자식 클릭");
};

이벤트 핸들러 네이밍 컨벤션

// 이벤트 핸들러 이름은 handle + 동작으로
const handleClick = () => {};
const handleSubmit = () => {};
const handleChange = () => {};
const handleInputChange = () => {};  // 더 구체적으로

// Props로 전달할 때는 on + 동작으로
<Button onClick={handleClick} />
<Form onSubmit={handleSubmit} />
<Input onChange={handleChange} />

운영자 실전 노트

실제 프로젝트 진행하며 겪은 문제

  • 이벤트 핸들러 네이밍 혼란 → handle로 시작(실제 함수), on으로 시작(Props 전달) 규칙 정립
  • 인라인 함수 남발로 성능 저하 → useCallback으로 최적화 필요

이 경험을 통해 알게 된 점

  • 이벤트 버블링 이해가 복잡한 UI 구조 설계에 필수다
  • stopPropagation보다 구조 개선이 근본적 해결책이다

다음 단계

이벤트 처리를 배웠으니 다음 글에서는 State를 본격적으로 다룬다. 여기서 useState를 잠깐 썼는데 다음 글에서 제대로 배운다. 이벤트랑 State를 같이 쓰면 진짜 동적인 앱을 만들 수 있다.


React 시리즈 탐색:

← 블로그 목록으로