커스텀 이벤트로 다른 컴포넌트에서 이벤트 트리거하기
어떤 컴포넌트가 갖고 있는 함수나 메서드를 다른 컴포넌트에서 트리거시키는 것은 쉽지 않다.
어떤 컴포넌트가 갖고 있는 함수나 메서드를 다른 컴포넌트에서 트리거시키는 것은 쉽지 않다.
Vue 프레임워크에서는 provide/inject 메서드를 이용해서 주입하고, 원하는 컴포넌트에서 꺼내와서 쓸 수도 있겠다. 하지만 이것도 완전히 자유로울 수는 없다. 결국 어떤 위치에서 주입을 해줘야 사용할 수 있고, 사용하는 측에서도 허용하는 스코프 내에서만 사용이 가능하기 때문이다.
그렇다면 어떻게 원하는 컴포넌트에서 복잡한 상태 혹은 다른 컴포넌트 내부에서만 다루어지는 상태과 엮인 함수나 메서드를 트리거시킬 수 있을까?
window, document는 클라이언트 사이드라면 어떤 컴포넌트에서도 접근할 수 있다. 즉, 이 위치에 이벤트리스너를 등록해둔다면, 어디서든 접근할 수 있을 것이다.
window.addEventListener(<EventType>, <EventHandler>)하지만 이벤트의 타입은 어떻게 정할 수 있을까. click, scroll 등의 보편적인 이벤트 타입으로는 원하는 순간에만 이벤트를 트리거시키기는 어려울 것이다. 이럴 때 사용할 수 있는 도구가 커스텀 이벤트다.
Subscribe
커스텀 이벤트는 자바스크립트에서 개발자가 직접 정의하고 제어할 수 있는 사용자 지정 이벤트다. 기본으로 제공되는 내장 이벤트 외에, 애플리케이션의 특정 요구사항에 맞는 고유 상호작용을 정의할 수 있도록 해준다. 이를 이용하면 애플리케이션의 다양한 컴포넌트 간의 특정 변경사항을 알리는 유연한 통신 메커니즘을 구축할 수 있고, 심지어 이벤트에 특정 데이터를 담아 전달할 수도 있다.
// CompA.tsx
const CompA = () => {
const [count, setCount] = useState<number>(0);
const countUp = (event) => {
setCount((prev) => prev + 1);
console.log(event.detail.message);
}
const countDown = (event) => {
setCount((prev) => prev - 1);
console.log(event.detail.message);
}
useEffect(() => {
window.addEventListener('custom-count-up', countUp);
window.addEventListener('custom-count-down', countDown);
return () => {
window.removeEventListener('custom-count-up', countUp);
window.removeEventListener('custom-count-down', countDown);
};
});
return (
<div>
<span>Hello world.</span>
<span>{count}</span>
</div>
);
};
export default CompA;// CompB.tsx
const CompB = () => {
const countUp = () => {
const countUpEvent = new CustomEvent('custom-count-up', {
bubbles: true,
detail: {
message: 'count up by CompB'
}
});
window.dispatchEvent(countUpEvent);
};
const countDown = () => {
const countDownEvent = new CustomEvent('custom-count-down', {
bubbles: true,
detail: {
message: 'count down by CompB'
}
});
window.dispatchEvent(countDownEvent);
};
return (
<div>
<button onClick={countUp}>Count Up!</button>
<button onClick={countDown}>Count Down!</button>
</div>
);
};
export default CompB;CompA와 CompB가 멀리 떨어진 곳에 위치해도, CompA의 내부에서 정의된 이벤트를 CompB에서 트리거시킬 수 있다. 이 예제는 간단한 역할을 하고 있으므로 React Context나 상태 관리 라이브러리를 사용해도 될 것이다. 하지만, 스토어에 넣고 싶지 않다거나, 컴포넌트에서만 갖고 있는 상태가 복잡하게 엮인 함수나 메서드를 트리거시키고 싶다면 그럴 때 이 커스텀 이벤트가 도움이 될 것이다.
그렇지만 이렇게 커스텀 이벤트를 종종 만들어 쓰기에는, 보일러 플레이트 코드가 다소 많다고 느껴진다. 다음과 같이 간단히 커스텀 훅 혹은 컴포저블로 추상화시켜서 사용한다면 조금 더 편리하게 사용할 수 있을 것이다.
interface ICustomEventOptions extends CustomEventInit {
bubbles?: boolean
cancelable?: boolean
composed?: boolean
}
interface IUseCustomEventParams<T> {
eventTypeKey: string
targetElement?: Element | Window
customEventHandler?: (e: CustomEvent<T>) => void
}
interface ITriggerCustomEvent<T> {
data?: T
eventOptions?: ICustomEventOptions
}
export const useCustomEvent = <T extends any>({
eventTypeKey,
targetElement,
customEventHandler
}: IUseCustomEventParams<T>) => {
const [isEventAttached, setIsEventAttached] = useState<boolean>(false)
const triggerCustomEvent = (param: ITriggerCustomEvent<T> = {
eventOptions: { bubbles: true }
}) => {
const customEvent = new CustomEvent<T>(eventTypeKey, {
...param.eventOptions,
detail: param.data
})
if (targetElement) targetElement.dispatchEvent(customEvent)
else window?.dispatchEvent(customEvent)
}
const attachCustomEvent = () => {
try {
if (targetElement) targetElement.addEventListener(eventTypeKey, customEventHandler as EventListener)
else window?.addEventListener(eventTypeKey, customEventHandler as EventListener)
setIsEventAttached(true)
} catch (e) {
console.error(`Failed to attach custom event '${eventTypeKey}':`, e)
setIsEventAttached(false)
}
}
const detachCustomEvent = () => {
try {
if (targetElement) targetElement.removeEventListener(eventTypeKey, customEventHandler as EventListener)
else window?.removeEventListener(eventTypeKey, customEventHandler as EventListener)
setIsEventAttached(false)
} catch (e) {
console.error(`Failed to detach custom event '${eventTypeKey}':`, e)
}
}
const updateTargetElement = (newTarget: Element | Window | null) => {
if (targetElement) {
detachCustomEvent()
}
targetElement = newTarget || window
attachCustomEvent()
}
return { triggerCustomEvent, attachCustomEvent, detachCustomEvent, isEventAttached, updateTargetElement }
}커스텀 이벤트는 애플리케이션에서 컴포넌트 간의 통신을 더욱 유연하고 효율적으로 만들어줄 수 있는 도구다. 특히, 복잡한 상태 관리나 글로벌 상태를 사용하지 않고도 떨어진 컴포넌트 간의 상호작용을 구현할 수 있다. 특히 이를 추상화시켜 커스텀 훅 및 컴포저블로 만들어둔다면, 대규모 애플리케이션에서 컴포넌트 간 결합도를 낮추고 코드의 가독성을 높이는 데 더욱 쉽고 간결하게 컴포넌트 통신을 다룰 수 있다.
References
https://ko.javascript.info/dispatch-events