이벤트 속성 이해하기

Created
May 12, 2024
Tags
React

모든 HTML 요소는 onmouseenter, onmouseover처럼 'on'으로 시작하는 속성을 제공하는데, 이를 이벤트 속성이라고 한다.

이벤트

사용자가 화면 UI에서 버튼을 클릭하거나 텍스트를 입력하는 등의 행위가 일어날 때 이벤트가 발생했다고 표현한다.

Event 타입

브라우저의 JavaScript 엔진은 Event 타입을 제공한다.

종류
설명
type
이벤트 이름으로 대소문자를 구분하지 않는다.
inTrusted
이벤트가 브라우저에서 발생한 것인지(true), 프로그래밍으로 발생한 것인지(false)를 판단한다.
target
이벤트가 처음 발생한 HTML 요소이다.
currentTarget
이벤트의 현재 대상, 즉 이벤트 버블링 중에서 이벤트가 현재 위치한 객체이다.
bubbles
이벤트가 DOM을 타고 버블링될지 여부를 결정한다.

다음 코드는 type 속성 값이 clickEvent 객체를 생성하는 예제이다. 이렇게 생성된 Event 타입 객체를 어떻게 처리할까?

new Event('click', { bubbles: true });

EventTarget 타입

모든 HTML 요소는 HTMLElement 상속 타입을 가진다. 그리고 HTMLElement는 최상위 EventTarget 타입을 시작으로 Node, Element와 같은 타입을 상속한다. 즉, 모든 HTML 요소는 EventTarget 타입이 정의하는 속성과 메서드를 포함하고 있다.

이벤트 처리기

EventTargetaddEventListener(), removeEventListener(), dispatchEvent()라는 3개의 메서드를 제공한다. 이름에서 ‘EventListener’는 ‘이벤트를 잘 듣고 있는 존재’ 정도로 직역할 수 있겠다. 이벤트가 발생할 때까지 잘 듣고 기다리다가 이벤트가 발생하면 해당 이벤트를 코드 쪽으로 알려주는 역할을 한다. 이를 이벤트 처리기(Event Handler)라고 한다. 사용법은 아래와 같다.

DOM객체.addEventListener(이벤트이름: string, 콜백 함수: (e: Event) => void);

브라우저 객체 모델의 window 객체는 Window 타입이고 Window 타입은 EventTarget 타입을 상속한다. 즉, window 객체는 addEventListener() 메서드를 제공하므로 아래처럼 작성하는 것도 가능하다.

window.addEventListener('click', (e: Event) => console.log('mouse click.'));

document.getElementById('root')?.addEventListener('click', (e: Event) => {
  const {isTrusted, target, bubbles} = e;
  console.log('mouse click.', isTrusted, target, bubbles);
});

export default function EventListener() {
  return <div>Event Listener!</div>;
}

click 이벤트는 웹 브라우저에서 발생했으므로 이벤트의 isTrusted 속성 값은 true, 마우스 클릭이 <div>Event Listener!</div>에서 일어났으므로 target<div> 요소가 되며, bubbles 값은 true임을 알려 준다.

DOM 객체의 이벤트 속성

앞에서 살펴본 addEventListener() 메서드는 사용법이 조금 번거롭다. 이 때문에 window를 포함한 대부분의 HTML 요소는 onclick처럼 ‘on’ 뒤에 이벤트 이름을 붙인 속성을 제공한다. 이벤트 속성은 addEventListener()의 사용법을 간결하게 하는 게 목적이므로 이벤트 속성 값에는 항상 이벤트 핸들러를 설정해야 한다.

const rootDiv = document.getElementById('root');

if (rootDiv) {
  rootDiv.onclick = (e: Event) => {
    const {isTrusted, target, bubbles} = e;
    console.log('mouse click occurs on rootDiv.', isTrusted, target, bubbles);
  };

  rootDiv.onclick = (e: Event) => {
    const {isTrusted, target, bubbles} = e;
    console.log('mouse click also occurs on rootDiv.', isTrusted, target, bubbles);
  };
}

이때 코드에서 10행의 내용은 출력되지만 5행의 내용은 출력되지 않는다. 이는 addEventListener()와 달리 onclick은 가장 마지막에 설정된 콜백 함수를 호출한다는 것을 의미한다.

리액트 프레임워크의 이벤트 속성

지금까지 DOM 객체를 대상으로 한 이벤트 처리 방법을 알아보았다면, 이제 리액트 방식으로 이벤트 처리하는 방법을 알아볼 것이다. 리액트도 비슷하다. 'on' 뒤에 이벤트명 형태로 된 HTML 요소의 이벤트 속성들을 제공한다. 한 가지 큰 차이는 카멜 표기법을 사용한다는 것이다. 그리고, 콜백 함수는 매개변수 e의 타입이 Event가 아니라 리액트가 제공하는 SyntheticEvent 타입을 설정해야 한다.

interface BaseSyntheticEvent<E = object, C = any, T = any> {
	nativaEvent: E;
	currentTarget: C;
	target: T;
	preventDefault(): void;
	stopPropagation(): void;
}
  • 리액트는 DOM에서 일어나는 이벤트를 nativeEvent라고 한다. nativeEvent 속성은 DOM에서 발생하는 이벤트의 세부 타입인 PointerEvent와 같은 이벤트 객체를 저장하는 데 사용한다.
  • currentTarget 속성은 이벤트 버블링 과정에서 현재 이벤트를 수신한 DOM 객체를 알고 싶을 때 사용한다.
  • target 속성은 이벤트를 처음 발생시킨 DOM 객체를 알고 싶을 때 사용한다.
  • preventDefault(), stopPropagation() 메서드는 아래에서 자세하게 다루겠다.

EventTaget의 dispatchEvent 메서드

EventTargetdispatchEvent() 메서드는 DOM 요소나 이벤트 타겟 객체가 지정된 이벤트를 발생시키는 데 사용된다. 이 메서드는 주어진 이벤트를 현재 EventTarget에 디스패치하고, 해당 이벤트를 이벤트 캡처링 및 버블링 단계를 통해 처리하도록 트리거한다.

eventTarget.dispatchEvent(event);

여기서 eventTarget은 이벤트를 발생시킬 DOM 요소나 이벤트 타겟 객체를 가리키고, event는 발생시킬 이벤트 객체이다.

예를 들어, 버튼을 클릭할 때 발생하는 클릭 이벤트를 JavaScript로 프로그래밍적으로 발생시키고 싶을 때 dispatchEvent() 메서드를 사용할 수 있다.

const button = document.getElementById('myButton');

function handleClick() {
  console.log('Button clicked!');
}

// 클릭 이벤트를 프로그래밍적으로 발생시키기
const clickEvent = new Event('click');
button.addEventListener('click', handleClick);

// 버튼 요소에 대해 dispatchEvent 메서드를 호출하여 클릭 이벤트를 발생시킴
button.dispatchEvent(clickEvent);

이 코드는 버튼 요소에 클릭 이벤트를 등록하고, 프로그래밍적으로 click 이벤트를 발생시켜 handleClick 함수를 호출한다. 이렇게하면 사용자가 실제로 버튼을 클릭하는 것과 동일한 동작이 발생합니다.

🔍
즉, dispatchEvent() 메서드의 목적은 크게 다음과 같다. 첫째로 프로그래밍적인 이벤트를 발생시키고 싶을 때이다. 사용자의 동작에 의해 발생하는 이벤트를 시뮬레이션하거나, 특정 조건이 충족될 때 이벤트를 발생시키는 등의 경우이다. 두번째로 단위 테스트나 통합 테스트에서 특정 이벤트가 발생했을 때 어떤 동작이 실행되는지를 확인하기 위해 사용될 수 있다.

이벤트 버블링

자식 요소에서 발생한 이벤트가 가까운 부모 요소에서 가장 먼 부모 요소까지 계속 전달되는 현상을 이벤트 버블링(Event Bubbling)이라고 한다. 이벤트 버블링은 이벤트가 발생한 요소에서 시작하여 상위 요소로 전파되는 방향이다.

import {SyntheticEvent} from 'react';

export default function EventBubbling() {
  const onDivClick = (e: SyntheticEvent) => {
    const {isTrusted, target, bubbles, currentTarget} = e;
    console.log('event bubbles on <div>', isTrusted, target, bubbles, currentTarget);
  };

  const onButtonClick = (e: SyntheticEvent) => {
    const {isTrusted, target, bubbles} = e;
    console.log('event starts at <button>', isTrusted, target, bubbles);
  };

  return (
    <div onClick={onDivClick}>
      <p>Event Bubbling</p>
      <button onClick={onButtonClick}>Click me!</button>
    </div>
  );
}

<button>에서 발생한 click 이벤트가 onButtonClick은 물론 상위 요소로 계속 전파되면서 부모인 <div>에 등록된 onClick 이벤트 핸들러인 onDivClick까지 실행된다. 그리고 target 값은 <button>이지만 currentTarget 값은 <div>로 서로 다르다. currentTarget은 이벤트의 현재 대상, 즉 이벤트 버블링 중 현재 이벤트가 위치한 객체를 가리킨다.

장점

이벤트 버블링은 HTML 요소 간에 이벤트 처리를 더욱 효율적으로 관리할 수 있도록 해준다. 예를 들어, 여러 요소가 동일한 이벤트를 처리해야 할 때 각 요소에 이벤트 핸들러를 개별적으로 등록하는 것보다 이벤트 버블링을 사용하여 최상위 요소에 한 번만 등록하는 것이 효율적이다.

단점

그러나 때로는 이벤트 버블링이 의도하지 않은 동작을 유발할 수도 있다. 이러한 경우에는 이벤트 버블링을 중지하거나 이벤트 전파를 막는 방법을 사용하여 문제를 해결할 수 있다.

stopPropagation 메서드와 이벤트 캡처링

stopPropagation() 메서드가 바로 이벤트 버블링이나 캡처링 단계에서 이벤트의 전파를 중지시키는 역할을 한다. 이 메서드를 호출하면 이벤트는 현재 요소에서 더이상 상위 요소로 전파되지 않는다.

이벤트 핸들러 내에서 stopPropagation() 메서드를 호출하면 이벤트가 전파되는 것을 중지하고 현재 요소에서 이벤트가 처리 된다.

만약 위의 예제에서 <button>에 등록된 onButtonClickstopPropagation() 메서드를 호출했다면, <button>의 부모 요소인 <div>까지 이벤트가 전달되지 않는다.

이벤트 캡처링

이벤트 캡처링(Event Capturing)은 이벤트가 최상위 요소에서부터 시작하여 실제 이벤트가 발생한 타깃 요소까지 이벤트의 전파 경로를 따라가는 현상이다. 즉, 이벤트가 발생한 요소를 향해 내려가는 방향이다.

이벤트 캡처링 단계에서는 이벤트가 최상위 요소부터 시작하여 하위 요소까지 전파됩니다. 각각의 부모 요소에서 이벤트 캡처링 단계에서 등록된 이벤트 핸들러가 실행되고, 최하위 요소인 이벤트 타깃 요소까지 이벤트가 전파됩니다.

<input> 요소의 이벤트 처리

<input>도 이벤트 핸들러를 꽤 많이 작성해야 하는 대표 요소이다. 다만 <input>type 속성 값에 따라 화면에 나타나는 모습과 사용자 입력을 얻는 방법이 조금 다르다.

<input type="text" placeholder="enter some texts" />
<input type="password" placeholder="enter your password" />
<input type="email" placeholder="enter your email address" />
<input type="range" />
<input type="button" value="I'm a button" />
<input type="checkbox" value="I'm a checkbox" defaultChecked />
<input type="radio" value="I'm a radio" defaultChecked />
<input type="file" />

<input>의 onChange 이벤트 속성

<input> 요소에 마우스 클릭이 일어나면 <button>과 마찬가지로 click 이벤트가 발생한다. 그런데 만약, 사용자의 입력이 텍스트라면 change 이벤트가 발생하며, 이 change 이벤트는 onChange 이벤트 속성으로 얻을 수 있다. 즉, <input> 요소의 onChange 이벤트 속성은 사용자가 입력 필드의 내용을 변경할 때 발생하는 이벤트를 처리하는 데 사용된다.

const onChange = (e: ChangeEvent<HTMLInputElement>) => {
	console.log(e.target.value);
}

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

위 코드에서 onChange 이벤트 핸들러의 매개변수 e 타입을 ChangeEvent<HTMLInputElement>로 설정했다. 따라서 HTMLInputElement 타입의 DOM 객체 값을 e.target 형태로 얻을 수 있는 것이다.

<input> 요소의 이벤트 관련 속성들

  1. onChange: 입력 필드의 값이 변경될 때 발생합니다. 주로 텍스트 입력 필드나 선택 상자 등의 값이 변경될 때 사용됩니다.
  2. onInput: 입력 필드의 값이 변경될 때 매번 발생합니다. onchange와 달리 사용자가 입력할 때마다 발생하므로 실시간으로 입력 내용을 처리할 때 유용합니다.
  3. onFocus: 입력 필드가 포커스를 받았을 때 발생합니다. 즉, 사용자가 해당 입력 필드를 클릭하여 입력할 수 있을 때 발생합니다.
  4. onBlur: 입력 필드가 포커스를 잃었을 때 발생합니다. 즉, 사용자가 해당 입력 필드를 떠났을 때 발생합니다.
  5. onKeyDown, onKeyPress, onKeyUp: 각각 키보드의 키를 누르는 순간, 문자가 입력될 때, 키를 놓을 때 발생합니다. 특정 키 입력에 반응하는데 사용됩니다.
  6. onSelect: 사용자가 입력 필드 내의 텍스트를 선택할 때 발생합니다.
  7. onChange: 파일 업로드 입력 필드에서 파일을 선택했을 때 발생합니다.
  8. onSubmit: <form> 요소 안에서 사용할 때, 사용자가 폼을 제출할 때 발생합니다.

<input>의 defaultValue와 defaultChecked 속성

valuechecked는 사용자가 입력한 값을 얻을 때 사용하고, defaultValuedefaultChecked는 어떤 초기값을 설정하고 싶을 때 사용한다.

  1. value: 사용자가 입력한 값을 나타낸다. 이는 주로 텍스트 입력 필드(<input type="text">, <textarea>)에서 사용된다. JavaScript에서는 이 속성을 사용하여 사용자가 입력한 값을 가져오거나 설정할 수 있다.
  2. checked: 체크박스나 라디오 버튼의 상태를 나타낸다. 체크박스가 선택되었을 때 true이고 선택되지 않았을 때 false이다. 라디오 버튼에서는 선택된 상태일 때만 true이고, 그 외에는 false입니다. JavaScript에서는 이 속성을 사용하여 체크박스나 라디오 버튼의 상태를 확인하거나 변경할 수 있다.
  3. defaultValue: 입력 필드의 초기값을 나타낸다. 이는 사용자가 입력 내용을 변경하기 전의 초기 상태를 나타낸다. JavaScript에서는 이 속성을 사용하여 초기값을 설정할 수 있다.
  4. defaultChecked: 체크박스나 라디오 버튼의 초기 선택 상태를 나타낸다. JavaScript에서는 이 속성을 사용하여 초기 선택 상태를 설정할 수 있다.

드래그 앤 드롭 이벤트 처리

모든 HTMLElement 상속 요소는 draggable이라는 boolean 타입 속성을 제공한다. <h1 draggable>Drag me<h1> 코드가 있을 때, <h1> 요소 위에서 마우스를 클릭한 채 드래그를 할 수 있다. 이때, 드래그 앤 드롭 관련 이벤트가 발생한다.

드래그 앤 드롭과 관련된 이벤트 핸들러들은 DragEvent 타입을 매개변수의 타입으로 사용한다. 아래에는 HTML과 리액트에서 사용하는 이벤트 속성들이다.

HTML
React 이벤트
설명
dragstart
onDragStart
드래그 작업이 시작될 때 발생한다. 드래그되는 요소에 대해 발생한다.
drag
onDrag
드래그하는 요소가 이동하는 동안 계속해서 발생한다.
dragenter
onDragEnter
드래그된 요소가 드롭할 대상 위로 올라갔을 때 발생한다.
dragover
onDragOver
드래그된 요소가 다른 요소 위로 움직일 때 발생한다. 이 이벤트를 처리하여 드롭이 허용되는지 여부를 결정할 수 있다.
dragleave
onDragLeave
드래그된 요소가 드롭할 대상을 떠날 때 발생한다.
drop
onDrop
드래그된 요소가 드롭 대상에 놓일 때 발생한다. 드롭 이벤트를 처리하여 요소를 해당 위치에 삽입하거나 처리할 작업을 수행한다.
dragend
onDragEnd
드래그 작업이 끝날 때 발생한다. 드래그가 완료되거나 취소될 때 모두 발생한다.

dragover(OnDragOver) 이벤트 핸들러

브라우저는 기본적으로 drop 이벤트가 발생하지 않도록 설계되어 있다. 브라우저에서는 사용자의 컴퓨터로 파일을 드래그하여 웹 페이지에 놓을 때와 같이 drop 이벤트가 발생하지 않는다. 이는 사용자가 실수로 개인 정보를 노출하거나 악의적인 코드를 실행하는 등의 문제를 방지하기 위함이라고 한다. 따라서 개발자가 드래그 앤 드롭 기능을 구현할 때, drop 이벤트가 발생하지 않기 때문에 명시적으로 이를 처리해야 한다. 방법은 아래와 같다.

  1. dragover 이벤트 처리: 먼저, dragover 이벤트를 사용하여 드래그된 요소가 드롭이 허용되는지 여부를 결정합니다. dragover 이벤트 핸들러에서 preventDefault()를 호출하여 드롭을 허용합니다.
  2. drop 이벤트 처리: drop 이벤트를 사용하여 요소가 드롭되었을 때 실행할 동작을 정의합니다.

preventDefault 메서드

드래그 앤 드롭 이벤트를 처리하려면 EventTarget 타입이 제공하는 preventDefault() 메서드를 알아야 한다. preventDefault() 메서드는 브라우저의 기본 동작을 취소하는 데 사용된다. 이 메서드를 호출하는 이유는 뭘까?

  1. 브라우저의 기본 동작 방지: 일부 브라우저에서는 드래그 앤 드롭 이벤트가 발생할 때 페이지의 스크롤이 자동으로 조정되는 경우가 있다. preventDefault() 메서드를 호출해서 이러한 동작을 방지할 수 있다.
  2. 드래그 앤 드롭 기본 동작 방지: 브라우저는 일반적으로 드래그 앤 드롭 동작이 발생할 때 해당 요소를 드래그하여 이동하거나 복사하는 등의 기본 동작을 수행한다. 이러한 동작을 원치 않는 경우, 예를 들어 사용자 정의된 드롭 동작을 구현하고자 할 때, preventDefault()를 호출하여 기본 동작을 취소할 수 있다.
  3. 드래그 앤 드롭 동작 제어: 드래그 앤 드롭 이벤트의 흐름을 제어하고 원하는 동작을 수행하기 위해 preventDefault()를 사용할 수 있다. 예를 들어, 드롭 영역에 드래그된 요소를 허용하기 전에 dragover 이벤트에서 preventDefault()를 호출하여 드롭을 허용하는 영역을 정의할 수 있다.