커스텀 이벤트(Custom Event)

지인혁·2023년 11월 15일
0
post-thumbnail
post-custom-banner

🤔 들어가며

데브코스 교육 중에 있어 history API로 SPA를 구현하고 있었다.

SPA를 구현하기 위해서 history API의 pushState 메소드를 통해 현재 URL 상태에 따라 라우팅해주는 함수를 전역적으로 모든 컴포넌트에서 사용하는 경우가 종종 발생했다.

전역적으로 사용하기 위해서 App.js의 최상위 컴포넌트에서 route 함수를 만들어 Props로 모두 전달해주면서 모든 컴포넌트가 route함수를 Props로 받게 되었다.

뭐 이렇게 해도 동작은 잘 한다. 하지만 컴포넌트 간의 깊이가 길어질 수록 Props는 증가하게 되고 각 컴포넌트는 독립성이 떨어지며 App.js에 대한 결합도가 높아지게 된다.

이를 해결하기 위한 방법 중 많은 방법이 있지만 커스텀 이벤트로 해결할 수 있었다. 커스텀 이벤트를 처음 본 나로써 정말 유용하게 사용할 수 있을 것 같아 포스팅을 한다.


커스텀 이벤트(Custom Event)

커스텀 이벤트(Custom Event)는 개발자가 직접 정의하여 사용할 수 있는 이벤트다.

브라우저가 제공하는 기본 이벤트 click, Keyup, load 등 외에 직접 이벤트를 정의 하고 생성하여 발생시킬 수 있다.

생성

커스텀 이벤트는 CustomEvent 생성자 함수를 사용하여 생성할 수 있다.

const customEvent = new CustomEvent("EventName", { 
  datail: { 
    key: "value" 
  } 
});

new 키워드와 함께 커스텀 이벤트를 생성하고 2개의 인자를 전달할 수 있다.

첫 번째 인자는 개발자가 정의할 커스텀 이벤트의 이름이다. 즉 click과 같은 새롭게 사용할 이벤트 이름이다.

두 번째 인자는 이벤트와 함께 전달하고 싶은 데이터를 담을 수 있다. 객체 안에 detail 프로퍼티 키의 값으로 생성한 이벤트에 데이터를 담아 둘 수 있다.

발생

이벤트를 만들었으면 해당 이벤트를 원하는 상황에서 발생시킬 수 있어야 한다. 이때 사용하는 dispatchEvent 메소드를 통해 생성한 이벤트를 발생시킬 수 있다.

const customEvent = new CustomEvent("EventName", { 
  datail: { 
    key: "value" 
  } 
});

window.dispatchEvent(customEvent);

window 최상위 객체에 dispatchEvent로 이벤트를 발생시키며 이전에 만든 customEvent를 인자로 전달하여 해당 이벤트를 발생 시킨다.

주의 할 점은 window 객체가 올 필요가 없다. 나는 전역적으로 사용하고 싶은 이벤트라 최상위 객체 window에 발생시킨 것 뿐이지 개발자가 원하는 document 객체에 이벤트를 유연하게 부착시키면 된다.

감지

이벤트를 만들고 발생시키면 해당 이벤트를 감지하여 로직을 수행할 수 있어야 한다.

우리가 흔히 본 addEventListner를 사용하면 되는데 첫 번째 인자로 원래 click이나 keyup 등 제공하는 이벤트 이름을 사용했지만 커스텀 이벤트를 생성할 때 지정한 이름으로 핸들러를 부착시키면 된다.

window.addEventListener("EventName", (e) => {
    const { key } = e.detail;
  
  	console.log(value); // "value"
});

const customEvent = new CustomEvent("EventName", { 
  datail: { 
    key: "value" 
  } 
});

window.dispatchEvent(customEvent);

동일하게 나는 전역적으로 사용하기 위해 window 객체에 커스텀 이벤트 핸들러를 부착했고 사용에 따라 원하는 document에 핸들러를 부착시키면 된다.

그리고 생성 때 데이터를 detail 객체에 전달해줬는데 이는 e.dtail 프로퍼티 키로 프로퍼티 값을 가져올 수 있고 활용할 수 있다.

그럼 route를 어떻게 전역적으로??

먼저 모든 컴포넌트에서 URL 변경이 필요한 시점에 dispatch 메서드로 URL 변경 이벤트를 발생시킬 수 있어야 한다.

그리고 window 객체가 URL이 변경하는 이벤트를 감지하고 URL을 변경시키며 변경된 URL을 통해 어떤 페이지를 보여줄지 라우팅 해야한다.

router.js

const ROUTE_CHANGE_EVENT_NAME = 'route-change';

export const initRouter = (onRoute) => {
    window.addEventListener(ROUTE_CHANGE_EVENT_NAME, (e) => {
        const { nextUrl } = e.detail;

        if (nextUrl) {
            history.pushState(null, null, nextUrl);
            onRoute();
        }
    });
};

export const push = (nextUrl) => {
    window.dispatchEvent(
        new CustomEvent(ROUTE_CHANGE_EVENT_NAME, {
            detail: { nextUrl },
        })
    );
};

여기서 onRoute() 함수는 URL에 따라 페이지를 보여줄 route 함수를 최상위 App.js에서 작성한 함수다.

App.js

this.route = () => {
    const { pathname } = window.location;
  
    if(pathname === "/") {
      	// root 페이지
    }
    else if(pathname.indexOf("/posts") === 0) {
    	// post 페이지
    }
}

initRouter(this.route);
this.route();

App.js 컴포넌트가 생성되면 window에 커스텀 이벤트를 부착해야해서 initRouter(this.route) 함수를 호출해준다.

$postList.addEventListener("click", (e) => {
    const $li = e.target.closest("li");

    if($li) {
        const { id } = $li.dataset;

        push(`/posts/${id}`);
    }
})

그리고 다른 하위 컴포넌트에서 커스텀 이벤트를 push 이벤트를 통해 dispatch 발생 시켜주기만 하면 전역적으로 route 함수를 커스텀 이벤트를 통해 사용할 수 있다.


👍 마치며

커스템 이벤트에 대해 알게 되었는데 멘토님께서 실무에서는 커스텀 이벤트를 많이 사용하는 편은 아니라고 했다.

하지만 새로운 기술을 알게 된 기분이였고 내가 가장 큰 관심을 가진 부분은 아마도 컴포넌트 간 Props를 줄이고 결합도를 낮추는 방법이지 않을까 싶다.

그래서 멘토님이 결합도를 낮추는 방법에는 옵저버 패턴, 싱글톤 패턴 등 다양한 방법이 있다고 알려주어서 이후 Notion 프로젝트에서는 커스텀 이벤트가 아닌 옵저버, 싱글톤 패턴을 활용해서 결합도를 낮췄다.

profile
대구 사나이
post-custom-banner

0개의 댓글