처음 HTML, CSS, JS를 공부하고 리액트를 처음 배우기 시작할 때에 이벤트 핸들러를 JSX 태그에 onClick={} 형식으로 인라인으로 달아주길래 각 태그에 이벤트 핸들러를 부착하는 줄 알고 있었다.
하지만 최근에 리액트에 대해서 더 공부를 해보면서 리액트는 이벤트를 위임 방식으로 효율적으로 처리한다는 것을 알게 되었다. 이번에는 리액트에서 이벤트를 어떻게 효율적으로 처리하는지를 알아보려고 한다.
React에서 이벤트를 처리하는 방법
Event
이벤트는 마우스를 클릭하거나 키보드를 누르는 것처럼 사용자의 액션에 의해 발생하거나, 비동기적 작업의 진행을 나타내는 등의 목적을 위해 API 들이 생성할 수도 있다. 한글로는 사건이라고 할 수 있다.
클릭 이벤트를 발생시키려면 어떤 요소를 클릭하거나, Element.click() 이나 window.dispatchEvent(new MouseEvent ('click')) 같이 프로그래밍적으로 발생시킬 수도 있다.
이벤트 핸들러
이벤트 핸들러는 이벤트가 발생했을 때 어떻게 처리할 것인지를 결정하는 함수다. 이 특정 요소가 클릭되었을 때 경고메시지를 출력하겠다. 라는 동작을 이벤트핸들러로 만들어볼 수 있다.
이렇게 이벤트는 이벤트를 발생시키고, 발생된 이벤트를 받아서 처리하는 이벤트 핸들러가 있다.
const button = document.querySelector('#myButton');
button.addEventListener('click', function() {
console.log('버튼이 클릭되었습니다!');
alert('버튼이 클릭되었습니다!');
});
리액트에서 이벤트 핸들러
아래는 간단한 버튼 클릭 이벤트를 등록하는 리액트 코드이다. 이처럼 리액트에서는 간단하게 이벤트에 대한 이벤트 핸들러를 등록할 수 있다.
export default function Button() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
리액트에서 이벤트를 처리하는 방법
리액트에서는 이벤트를 처리하는 방식을 내부적으로 최적화해서 동작한다. 그 중에서도 핵심이라고 생각되는 2가지 개념인 이벤트 합성 시스템과 위임 시스템에 대해서 알아보자.
이벤트 합성 시스템
만약 어떤 웹서비스를 개발했다고 가정했을 때, 크롬에서는 정상적으로 작동되지만 파이어폭스에서는 작동하지 않는 문제가 발생하면 어떨까? 이 상황에서는 파이어폭스에서도 일관적으로 작동하도록 코드를 수정해야할 것이다. 하지만 이런 과정은 매우 귀찮은 작업이다.
이런 상황을 최소화하기 위해서 리액트는 합성 시스템을 사용하여 이벤트가 브라우저 간에 일관되게 동작하도록 한다. 브라우저의 기본 이벤트 시스템을 감싸서 React가 실행되는 브라우저에 관계 없이 통일된 API를 제공한다.
통일된 API라고 한다면 여러 이벤트 객체에서 사용할 수 있는 공통 인터페이스로, e.preventDefault, e.stopPropagation 같은 메서드가 있다.
이런 문제를 크로스브라우징 문제라고 한다. 우리가 일반적으로 사용하는 Chrome이나 Firefox 같은 브라우저는 모두 표준을 빠르게 반영하는 브라우저들이지만 이들 간에도 크로스 브라우징 문제가 발생할 수 있고, 이 외에 다른 브라우저들 간에도 이러한 문제가 발생할 수 있다.
각 브라우저 별로 Javascript 최신 API 지원 여부라던지 CSS 속성의 구현이나 기본값의 차이가 있을 수 있다.
이벤트 위임 방식
이벤트 위임은 여러 요소에 대해 이벤트 핸들러를 각 요소별로 달아주는 방식이 아닌 그 여러 요소들의 공통 요소 하나에만 핸들러를 부착해서 이벤트를 처리하는 방식을 말한다.
이 방식이 가능한 이유는 이벤트는 상위 요소로 버블링, 전파되기 때문이다. 그래서 하위 요소에서 발생한 이벤트는 최상위 요소까지 전달이 된다.
이벤트 위임이라는 키워드만 들었을 때는 처음에 이해가 안됐었는데 생각보다 쉬운 개념이다. 하나하나 요소에 달아주려면 요소가 추가될 때마다 해당 요소에 맞는 이벤트핸들러를 추가해야 하는데, 위임 방식으로 하면 이벤트 핸들러 하나만 조작하면 되니 무척 관리가 편해진다.
이런 이벤트 위임 방식은 리액트가 이벤트를 효율적으로 처리할 수 있게 하는 핵심 원리이다. 리액트에서는 대부분의 이벤트를 최상위 루트 컴포넌트 레벨에서 처리한다. (원래는 document 하지만 리액트 17 이후 부터는 id='root'인 루트 컴포넌트에서 관리된다고 한다.)
리액트에서 예외적으로 onScroll 이벤트 등 일부 이벤트는 부착한 태그 내에서만 발생하고 전파되지 않는다.
아래는 예시로, 루트 컴포넌트 레벨에 이벤트 핸들러를 부착하고 처리한다.
static attachEventListeners(container) {
const eventTypes = ['click', 'change', 'submit'];
eventTypes.forEach(eventType => {
document.addEventListener(eventType, (event) => {
this.handleEvent(event, container);
});
});
}
리액트 코드에서 onClick 인라인 핸들러로 요소별로 핸들러를 부착하더라도 리액트는 이를 내부적으로 최적화해서 루트 컴포넌트 레벨의 이벤트 핸들러로 만들고, 이 핸들러에서 클릭된 요소를 파악하고 각 요소별로 원하는 동작을 할 수 있게 만들어준다.
이벤트 전파, 버블링을 막으려면
일반적으로 대부분의 이벤트는 전파가 되기 때문에 중첩되어있는 요소를 클릭할 경우 전파되어 이벤트 핸들러가 여러개 호출될 수 있다. 이런 경우에 전파를 막고 하위에 있는 요소의 핸들러만 실행하고 싶은 경우에는 `event.stopPropagation()` 을 호출하면 된다.
이렇게 되면 드롭 다운이나 모달 창을 구현했을 때 외부를 클릭하면 해당 컴포넌트가 사라지고, 내부를 클릭하면 사라지지 않고 내부 동작을 수행하게 할 수 있다.
성능상의 이점
리액트에서 위와 같은 방식들로 이벤트를 처리하면 개별적인 이벤트 리스너가 아닌 하나의 리스너로 관리하기 때문에 메모리를 아끼고, 이벤트 리스너의 오버헤드를 줄일 수 있다.
예시
See the Pen Untitled by JeongwooSeo (@ShipFriend0516) on CodePen.
요약
전반적으로 리액트는 이벤트 관련된 처리를 내부적으로 최적화를 해준다는 것을 알 수 있다. 이런 최적화를 개발자가 할 필요 없이 내부적으로 진행하기 때문에 코드의 양이 그만큼 줄어들고 신경써야할 부분이 줄어드는 것은 아주 좋은 것 같다.
최적화라는 것은 상황에 따라 다른 것이지만 일반적으로 대부분의 상황에서 리액트의 최적화는 매우 효율적으로 적용된다고 한다.
최근 멘토님에게 이런 내부 동작을 분석하고 학습하는 것이 현재에는 큰 도움이 되진 않지만 언젠가 문제가 발생했을 때 그때 빛을 발한다고 이야기를 해주신 것이 인상 깊었는데, 이런 이벤트 최적화도 특정 상황에서 하나의 최적화 수단이 될 수 있겠다는 생각이 든다.
'Web > Frontend' 카테고리의 다른 글
react-icons 라이브러리로 알아보는 트리 쉐이킹 (0) | 2024.11.03 |
---|---|
[React] useEffect 역할과 사용 방법 (1) | 2024.09.29 |
간단한 예제와 함께 알아보는 Flux 패턴 (0) | 2024.09.15 |
드래그 앤 드롭 기능 구현과 dragover 성능 최적화까지 (0) | 2024.09.08 |
oraciondev 님의 블로그 입니다.
안녕하세요