‘리액트로 사고하기’를 읽고 느낀 점과 배운 점이 많아서 스터디원들과 리액트 공식 문서를 읽어보기로 했다. 이벤트에 응답하기를 첫 번째로 읽어보았다. 리액트를 배우게 되면 자연스럽게 학습하던 내용들이라 어렵지는 않았다. 그래서 따로 방법론을 정리하지 않고, 내가 리액트를 배우면서 느낀 점과 궁금한 점을 정리해 보았다. # 이벤트 시스템
이벤트는 전면에서 사용자와 직접 소통하고 상호작용한다. 자바스크립트를 처음 배울 때는 직접 DOM을 선언하고 이벤트 리스너로 핸들러를 바인딩하는 명령형 방식으로 이벤트 처리를 해왔다.
// 전통적인 이벤트 바인딩 (명령형)
const button = document.getElementById('myBtn');
button.addEventListener('click', handleClick);
// 나중에 정리까지 하여 최적화
button.removeEventListener('click', handleClick);
//리액트는 이벤트 바인딩을 선언형으로 추상화 하였다.
// React 방식: 선언형
<button onClick={handleClick} >Click me</button>
자바스크립트로 배웠던 명령형 이벤트 바인딩보다 훨씬 코드 양도 줄고 가독성도 좋아 보인다. 다만, 여기에는 미세한 트레이드 오프가 있다.
리액트의 이벤트 시스템은 네이티브 DOM 이벤트를 SyntheticEvent로 감싸면서 제어권 일부를 포기하게 된다. 브라우저 간 호환성 문제를 해결하기 위해 모든 이벤트를 SyntheticEvent 객체로 래핑하여 실제 브라우저 이벤트에 접근하려면 e.nativeEvent로 접근해야 된다.
function handleMouseEvent(e) {
// SyntheticEvent에서는 접근 불가
console.log(e.movementX); // undefined
console.log(e.movementY); // undefined
// nativeEvent를 통해서만 접근 가능
console.log(e.nativeEvent.movementX); // 마우스 이동량
console.log(e.nativeEvent.movementY);
}
# 이벤트 전파
자바스크립트를 배우면서 이벤트 버블링이라는 개념을 접해봤을 것이다. 이벤트가 발생하면 이벤트가 발생한 타겟(태그) 부터 document까지 이벤트 트리거가 전파되어 사이에 있는 이벤트를 모두 실행시킨다. 리액트도 자바스크립트를 기반으로 동작하기 때문에 이벤트 버블링이 일어난다. 그래서 e.stopPropagation 함수를 호출해 자식 컴포넌트에서 발생한 이벤트 트리거가 부모 컴포넌트의 이벤트에 닿지 못하게 막을 수 있다.
이 문서에서는 부모 컴포넌트까지의 이벤트 전파를 무조건 막기보다 조건 처리를 통해 자식이 부모의 동작을 선택적으로 호출하게끔 하는 것을 권장한다고 한다. 자세한 설명이 없어 어떤식으로 제어를 할지 찾아보았다.
function AddToCartButton({ product }) {
return (
<button onClick={e => {
e.stopPropagation(); // 부모의 카드 클릭 이벤트를 무조건차단!
addToCart(product);
}}>
장바구니 담기
</button>
);
}
// 위 방식은 부모의 이벤트는 무조건 차단하게 됨으로 다른 컴포넌트에서
// AddToCartButton이 함수를 사용할 때 부모의 이벤트가 차단된 상태인지 예측하기 어렵다.
function AddToCartButton({ product, onClickFromP }) {
return (
<button onClick={e => {
e.stopPropagation(); // 우선 이벤트 전파를 막고
addToCart(product);
// 선택적으로 동작할 수 있게 설계
if(onClickFromP) {
onClickFromP()
}
}}>
장바구니 담기
</button>
);
}
# 사이드 이펙트
공식 문서에서 “이벤트 핸들러는 사이드 이팩트를 위한 최고의 위치”라고 표현했다. 함수를 렌더링하는 것과 다르게 순수할 필요가 없으므로 무언가를 변경하는데 최적의 위치이다.
이벤트 핸들러는 사용자와의 상호작용의 시작점이고, 이는 곧 데이터 변경을 의미한다. 즉, 사용자가 의도적으로 이벤트를 발생시키면서 사이드 이펙트가 실행되므로 실행시점을 명확하게 파악할 수 있다. 이는 디버깅에 이점이 있으며 언제 왜 이 사이드 이팩트가 실행되는지 명확하게 알 수 있다. 이렇게 사이드 이팩트를 이벤트 핸들러나 useEffect 등 명확한 경계에서 관리하면 컴포넌트 리렌더링 자체는 순수하게 유지하고 사이드 이팩트가 발생하는 위치를 구분할 수 있는 장점이 있다고 생각한다.
# 마치며
‘이벤트에 응답하기’ 섹션은 리액트를 배우면서 자연스럽게 알 수 있는 기본적인 이벤트 사용 방법론을 다루지만 정리하면서 나의 생각도 함께 정리할 수 있었다. 이벤트 버블링이 발생하면 그냥 타겟 컴포넌트에서 e.stopPropagation() 한줄로 전파를 막아 문제를 해결하곤 했었다. e.stopPropagation() 한 줄의 코드가 컴포넌트간에 결합도를 만든다는 점은 생각해 본적이 없었다. 또, 사이드 이펙트는 이벤트 핸들러에서 처리한다고만 배웠지 이번에도 왜? 그렇게 하는지는 의문 조차 갖지 않았다. 앞으로는 그냥 코드를 작성하는 개발자가 아니라 의문을 먼저 던지는 그런 개발자가 되어 보려고 노력하겠다.