React에서 이벤트 핸들러 함수를 선언할 때, 이벤트 타입 또는 함수 타입을 아래와 같이 선언한다.
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
//
};
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (event) => {
//
};
여기서 MouseEvent는 반드시 React.MouseEvent를 사용해야 한다. JS에서 기본으로 제공되는 MouseEvent를 사용하면 React에서 사용할 때 타입 에러가 발생한다. (단, DOM 요소에 직접 addEventListener를 사용할 때에는 기본 제공되는 MouseEvent를 사용해야 한다) 제네릭에는 어떤 요소의 onClick(또는 onMouseOver, onDrag 등도 포함)에 들어갈 이벤트 핸들러인지를 명시해서 더 타입을 명확하게 할 수 있다.
이렇게 작성하면 타입 에러 없이 event 객체에 접근할 수 있게 된다. 그러나 event 객체의 target은 EventTarget 타입인데, 이로 인해 Node 또는 Element 인터페이스의 메서드를 활용할 수 없다.
따라서 target.classList, target.closest(), target.childNodes 등의 Element, Node의 프로퍼티와 메서드들에 접근할 수 없다. 이러한 메서드에 접근하기 위해서는 타입 가드를 통해 타입을 좁혀야 한다.
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
if (!(target instanceof HTMLElement)) {
return;
}
const { classList } = target;
};
나는 지금까지 위 코드처럼 별 이유 없이 HTMLElement로 타입을 좁혔는데, 이게 의도치 않은 버그를 낳았다. 버튼을 누를 때 간헐적으로 이벤트 핸들러가 동작하지 않았다. 이유를 알 수 없어서 계속 버튼만 클릭하다가 버튼 안에 이미지를 누르면 이벤트가 동작하지 않는다는 것을 알게 되었다.
이 버튼의 안쪽에는 SVG 태그가 들어 있었는데, SVG가 HTMLElement 인터페이스를 상속받지 않아서 생긴 버그였다.
SVG태그는 HTMLElement가 아닌 SVGElement를 상속받는다. 이로 인해 버튼을 누를 때 SVG 이미지의 선(path)을 누르면 동작하지 않고, 이미지 외의 공간을 누를 때는 버튼의 이벤트 핸들러가 동작을 했던 것이다. 이벤트 핸들러에서 HTMLElement 대신 button과 svg의 가장 가까운 프로토타입 체인상의 공통 타입인 Element로 타입을 좁히니 해결되었다.
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
if (!(target instanceof Element)) {
return;
}
};
그 동안 타입 에러를 해결하는데만 급급해서 큰 고민 없이 아무 타입이나 사용해왔던 것 같다. 앞으로는 타입을 사용할 때 반드시 그 타입을 사용해야만 하는 이유를 먼저 찾고 적용해야겠다.
참고자료
https://developer.mozilla.org/en-US/docs/Web/API/SVGSVGElement
'연습장 > 낙서장' 카테고리의 다른 글
[JavaScript] Array.prototype.splice 직접 구현하기 (0) | 2022.04.12 |
---|---|
[JavaScript] Array.prototype.map 직접 구현하기 (0) | 2022.04.06 |