반응형

리액트 이벤트 처리 마스터하기 🎯:
초보자를 위한 실전 가이드

"onClick도 잘 모르겠는데, 이벤트 객체? this 바인딩?" 🤯
리액트 초보가 가장 헷갈리는 바로 그 주제!
이제는 명쾌하게 정리해드릴게요.
반응형

 

안녕하세요, 여러분! 😊

리액트를 처음 접하고 나서 가장 많이 헤매는 부분 중 하나가 바로 "이벤트 처리" 아닐까요?

JavaScript에서는 잘 되던 이벤트 등록이, 리액트에선 갑자기 JSX 문법이니 이벤트 객체니 하면서 헷갈리는 경우 많죠.

이번 글에서는 리액트의 이벤트 시스템을 차근차근 정리하고, 자주 실수하는 포인트도 함께 짚어드릴게요.

실습 위주로 따라오시면, 글 다 읽고 나면 자신 있게 "이벤트 처리? 그건 이제 껌이죠!"라고 말하실 수 있을 거예요.

그럼 시작해볼까요? 😊

 

1. 리액트의 기본 이벤트 바인딩 방식 🧷

리액트에서 이벤트를 다루는 방식은 일반 HTML과 다소 다릅니다.

JSX 문법을 따르기 때문에 이벤트 속성을 onclick이 아닌 onClick처럼 카멜케이스(camelCase)로 작성해야 하고, 실행할 함수는 문자열이 아닌 함수 참조로 전달해야 해요.

예제 코드 👇

function ClickButton() {
  function handleClick() {
    alert('버튼이 클릭되었어요!');
  }

  return (
    <button onClick={handleClick}>
      클릭해보세요!
    </button>
  );
}

 

위 코드에서 onClick={handleClick}처럼 함수 이름만 넘기고 괄호는 붙이지 않죠?

괄호를 붙이면 컴포넌트 렌더링 시점에 함수가 실행돼버리니까 꼭 조심해야 해요!

초보자 주의 포인트 📌

  • JSX에서는 이벤트 이름을 반드시 카멜케이스로 작성해야 합니다.
  • 이벤트 핸들러는 함수 참조로 전달해야 하며, 함수 호출문이 들어가면 안 돼요!

잘못된 예와 올바른 예 🆚

잘못된 방식 올바른 방식
onclick="handleClick()" onClick={handleClick}
onClick={handleClick()} onClick={() => handleClick()}

익명 함수로 감싸는 방식은 상황에 따라 사용하면 유용하지만, 함수 내부에서 props나 state를 다뤄야 할 때만 사용하는 게 좋아요!

 

 

2. SyntheticEvent란? 🤖 진짜 이벤트 객체와의 차이

리액트에서 이벤트를 처리할 때 SyntheticEvent라는 개념이 등장해요.

"이게 뭐야?" 싶으실 텐데, 쉽게 말해 브라우저의 native event를 감싼 리액트 전용 래퍼 객체라고 생각하시면 됩니다.

왜 SyntheticEvent를 쓸까?

  • 브라우저 간 이벤트 처리 방식이 달라서, 이를 통합하기 위해 리액트가 만든 표준화된 이벤트 객체예요.
  • stopPropagation()이나 preventDefault() 같은 메서드도 동일하게 사용할 수 있어요.

SyntheticEvent의 특징 🌐

기능 설명
통합 API 브라우저마다 다른 이벤트 처리 방식을 하나의 인터페이스로 제공
자동 메모리 최적화 이벤트 객체는 자동으로 풀(pool)에 반환되어 재사용됨
비동기 코드에서의 문제 setTimeout 등에서 접근하면 이벤트 객체가 null로 초기화되어 있음

Tip! 이벤트 객체를 비동기적으로 사용해야 할 경우 e.persist()를 꼭 호출하세요!

예시 코드 👇

function LogEvent() {
  function handleClick(e) {
    e.persist(); // SyntheticEvent 풀 반환 방지
    setTimeout(() => {
      console.log(e.type); // 'click'
    }, 1000);
  }

  return (
    <button onClick={handleClick}>이벤트 타입 출력</button>
  );
}

 

이처럼 SyntheticEvent는 리액트의 퍼포먼스 최적화를 위한 핵심 설계 중 하나이며, JS 이벤트와 매우 유사하지만 React 내부에서 관리된다는 점을 꼭 기억해 주세요!

 

 

3. 이벤트 객체 다루기 – e.preventDefault(), e.target 등 🧪

리액트 이벤트 핸들러에서 전달받는 e는 앞서 배운 SyntheticEvent 객체입니다.

이 객체를 활용하면 preventDefault()로 기본 동작을 막거나, e.target을 통해 이벤트가 발생한 요소에 접근할 수 있어요.

📌 주요 이벤트 객체 프로퍼티/메서드 요약

속성/메서드 설명
e.preventDefault() 기본 동작(예: 폼 제출, 링크 이동 등)을 막습니다.
e.stopPropagation() 이벤트 버블링(상위 요소로 전파)을 막습니다.
e.target 이벤트가 실제로 발생한 DOM 요소를 가리킵니다.
e.currentTarget 이벤트 핸들러가 바인딩된 요소를 가리킵니다.

🎯 예제 1 – 기본 동작 막기 (preventDefault)

function FormExample() {
  function handleSubmit(e) {
    e.preventDefault();
    alert('폼 제출이 막혔어요!');
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" placeholder="이름 입력" />
      <button type="submit">제출</button>
    </form>
  );
}

 

이 예제는 기본적으로 페이지 리로드를 막고, alert만 출력해요.

만약 e.preventDefault()를 생략하면 폼이 제출되면서 페이지가 새로고침됩니다.

🎯 예제 2 – target과 currentTarget 구분

function TargetExample() {
  function handleClick(e) {
    console.log('target:', e.target.tagName);
    console.log('currentTarget:', e.currentTarget.tagName);
  }

  return (
    <div onClick={handleClick} style={{ padding: '1em', border: '1px solid #ccc' }}>
      <button>버튼 클릭!</button>
    </div>
  );
}

 

이 코드에서 e.target실제 클릭된 버튼이고,

e.currentTargetonClick이 바인딩된 div를 가리킵니다.

DOM 이벤트 흐름을 이해할 때 아주 중요한 개념이에요!

 

💡 정리!

이벤트 객체를 잘 활용하면 복잡한 사용자 상호작용도 깔끔하게 구현할 수 있어요.

특히 targetcurrentTarget의 차이는 반드시 숙지해두세요!

 

 

4. this 바인딩 문제 해결법과 화살표 함수 사용법 🧭

리액트에서 이벤트 핸들러를 작성하다 보면 thisundefined가 되는 문제가 발생하곤 해요.

특히 클래스형 컴포넌트를 사용할 때 this.handleClick처럼 핸들러를 호출하려고 하면 에러가 발생할 수 있죠.

🎯 클래스형 컴포넌트에서의 this 바인딩

class EventExample extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.handleClick = this.handleClick.bind(this); // 바인딩 필수
  }

  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        클릭 횟수: {this.state.count}
      </button>
    );
  }
}

 

this.handleClick을 이벤트에 넘기기 전에 생성자에서 바인딩을 꼭 해줘야 해요.

그렇지 않으면 this가 undefined가 되어 this.setState가 작동하지 않아요!

💡 화살표 함수로 해결하기

class ArrowEventExample extends React.Component {
  state = { count: 0 };

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        클릭 횟수: {this.state.count}
      </button>
    );
  }
}

 

handleClick화살표 함수로 선언하면 생성자에서 바인딩하지 않아도 this가 자동으로 바인딩돼요.

요즘은 이 방식이 더 보편적으로 쓰이고 있어요!

🚀 함수형 컴포넌트에서는?

함수형 컴포넌트에서는 this를 쓰지 않기 때문에 바인딩 문제가 아예 없습니다!

대신 useState, useCallback 등 훅을 적절히 활용해서 상태나 함수를 관리하면 돼요.

function FunctionalEvent() {
  const [count, setCount] = React.useState(0);

  const handleClick = () => {
    setCount(prev => prev + 1);
  };

  return (
    <button onClick={handleClick}>
      클릭 횟수: {count}
    </button>
  );
}

 

🔥 핵심 요약:

클래스형 컴포넌트는 this 바인딩이 필수!

하지만 함수형 컴포넌트에서는 바인딩 걱정 없이 깔끔하게 이벤트를 처리할 수 있어요.

이제 this 때문에 헤맬 일은 없겠죠?

 

 

5. 커스텀 이벤트 핸들러 구조 설계하기 🛠️

리액트에서 이벤트 처리는 단순히 onClick={() => ...}처럼 인라인으로 끝낼 수도 있지만, 규모가 커질수록 이벤트 핸들러의 구조화가 굉장히 중요해집니다.

컴포넌트가 많아지고 이벤트가 늘어나면 로직 재사용유지보수에 큰 차이를 만들어요.

💡 커스텀 핸들러를 분리하는 이유

  • 핸들러 로직이 복잡해질 경우 읽기 어려운 JSX가 되기 쉽습니다.
  • 여러 컴포넌트에서 같은 로직을 재사용할 때 중복을 피할 수 있습니다.

🧩 예제: 입력 값 검증 핸들러

function useInputValidator() {
  const validate = (value) => {
    if (value.length < 3) {
      return "3자 이상 입력해주세요!";
    }
    return "";
  };

  return validate;
}

function InputForm() {
  const [input, setInput] = React.useState("");
  const [error, setError] = React.useState("");
  const validate = useInputValidator();

  const handleChange = (e) => {
    const val = e.target.value;
    setInput(val);
    setError(validate(val));
  };

  return (
    <div>
      <input value={input} onChange={handleChange} />
      {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
  );
}

 

위 코드에서는 useInputValidator라는 커스텀 훅으로 입력값 검증 로직을 분리했어요.

핸들러 함수는 깔끔하게 유지되고, 검증 로직은 재사용 가능합니다.

⚙️ 다양한 상황에 맞는 이벤트 구조 설계 팁

상황 설계 팁
반복적인 입력 검증 커스텀 훅으로 검증 함수 모듈화
복잡한 버튼 액션 핸들러에서 여러 유틸 함수를 조합
다양한 트리거로 같은 동작 공통 로직을 별도 핸들러로 추출

🚀 요약!

리액트 이벤트 처리를 고수처럼 만들고 싶다면, 핸들러를 짧고 간결하게 유지하고, 복잡한 로직은 커스텀 훅이나 함수로 분리해보세요.

유지보수가 10배 쉬워집니다!

 

 

6. 예제로 알아보는 이벤트 처리 베스트 프랙티스 🧠

리액트의 이벤트 시스템을 이론적으로 이해하는 것도 중요하지만, 예제를 통해 감을 잡는 것이 훨씬 효과적입니다.

이번에는 실무에서 자주 등장하는 이벤트 시나리오 3가지와 그에 맞는 처리 방법을 살펴보겠습니다.

🧪 예제 1: 인풋 실시간 반영 + 엔터키 제출

function ChatInput() {
  const [message, setMessage] = React.useState("");

  const handleChange = (e) => setMessage(e.target.value);

  const handleKeyDown = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      alert(`메시지 전송: ${message}`);
      setMessage("");
    }
  };

  return (
    <textarea
      value={message}
      onChange={handleChange}
      onKeyDown={handleKeyDown}
      placeholder="메시지를 입력하고 Enter를 눌러보세요"
    />
  );
}

 

이벤트를 중첩 조건으로 처리할 수 있고, preventDefault로 기본 동작을 제어할 수 있다는 것을 보여주는 예제예요.

🧪 예제 2: 동적으로 생성된 리스트 항목 클릭

function DynamicList() {
  const items = ["사과", "바나나", "오렌지"];

  const handleClick = (item) => {
    alert(`${item}을 선택하셨습니다!`);
  };

  return (
    <ul>
      {items.map((item, idx) => (
        <li key={idx} onClick={() => handleClick(item)}>
          {item}
        </li>
      ))}
    </ul>
  );
}

 

이벤트 핸들러에 익명 함수를 사용해서 인자를 넘기는 패턴이에요.

많이 쓰이는 패턴이지만 렌더링 성능에 영향을 줄 수 있으니 상황에 따라 useCallback도 고려해보세요!

🧪 예제 3: 부모에서 자식 이벤트 제어

function ParentComponent() {
  const handleChildClick = (msg) => {
    console.log("자식 클릭됨:", msg);
  };

  return <ChildComponent onCustomClick={handleChildClick} />;
}

function ChildComponent({ onCustomClick }) {
  return (
    <button onClick={() => onCustomClick("Hello from Child")}>
      자식 버튼
    </button>
  );
}

 

부모가 자식의 이벤트를 제어할 수 있도록 props로 핸들러를 전달하는 전형적인 패턴입니다.

컴포넌트 간 상호작용을 설계할 때 자주 사용되죠.

✅ 실전에서 꼭 기억할 베스트 프랙티스

  • 이벤트 핸들러는 짧고 명확하게 구성하자.
  • 중복되는 로직은 커스텀 훅이나 외부 함수로 분리하자.
  • props로 이벤트를 넘길 때는 이름을 명확하게 지정하자 (onClick보다는 onItemSelect 등).

🎉 결론!

이벤트 처리를 잘하면 리액트 앱의 UX, 유지보수, 성능까지 모두 향상됩니다.

여기까지 익히셨다면 이제 실전에서도 막힘 없이 이벤트를 자유자재로 다루실 수 있어요!

 

🔚 이벤트, 이제는 두렵지 않다!

여기까지 따라오시느라 정말 수고 많으셨어요!

처음엔 onClick 하나도 헷갈리던 리액트 이벤트 처리 방식, 이제는 꽤나 자신감이 붙지 않으셨나요?

이번 글을 통해 이벤트 객체의 구조부터, this 바인딩 문제, 커스텀 핸들러 설계, 실전 예제까지 모두 다뤄봤습니다.

앞으로는 이벤트 처리 코드를 짤 때,

"이걸 함수로 빼면 더 깔끔할까?",

"preventDefault 꼭 써야 할까?"

같은 고민을 스스로 던지고 답할 수 있게 되실 거예요.

 

다음엔 폼 처리, 상태 동기화 등 더 깊은 리액트 주제로 다시 찾아올게요.

놓치지 않도록 구독이나 북마크 해두시는 거 잊지 마시구요! 😊

반응형

+ Recent posts