이번 프로젝트를 진행하면서, 프로젝트에 심어놓은 GTM
이 클릭한 id를 추적하지 못하는 이슈가 등장했다.
특히 이상했던 점은 어떤 것은 추적하고, 어떤 것은 추적하지 못하였다.
심지어 같은 버튼을 클릭해도 잡힐 때와, 안 잡힐 때가 있었다.
인터넷이 불안정해서 못잡는 건가..? 고민도 해보았으나, 그럴 리가 없지.
이를 해결하기 위해 GTM
을 살펴보았다.
GTM에 관련해서는 GTM, Google Tag Manager 뜯어보기 글에 설명이 너무 잘 되어 있다.
해당 글에서 가장 주의깊게 보았던 부분은 아래와 같다.
GTM은 dataLayer라는 자바스크립트 전역 변수를 감시하고 있으며, 이 변수에 데이터가 들어오는 것을 감지하여 트리거를 발동하거나 변수에 값을 할당하고 있다.
모든 이벤트는 dataLayer
에 저장되고, dataLayer
를 통해 트리거
가 발동된다.
이를 확인하기 위해서는 GTM을 심은 프로젝트에서 아무 데나 클릭 후,
콘솔창에 dataLayer
만 입력하면 수집한 이벤트를 확인할 수 있다.
이제 클릭한 후에 dataLayer
를 살펴보자.
아래 사진에서 빨간색이 글자를 클릭했을 때, 초록색이 글자 옆 공간을 클릭했을 때이다.
그리고 해당 Button
컴포넌트는 아래와 같이 구성되어 있다.
//Button.tsx
const Button = ({
children, //
width = '60px',
height = '36px',
onClick,
leftIcon,
color = 'primary',
id,
loading = false,
}: ButtonProps) => {
return (
<Style.Button width={width} height={height} color={color} onClick={onClick} disabled={color.includes('disabled') || loading} id={id}>
<Style.InnerText>
{loading ? (
<Style.LoadingCircle color={color}>
<circle cx="50%" cy="50%" r="10"></circle>
</Style.LoadingCircle>
) : (
<>
{leftIcon && <Style.Icon>{leftIcon}</Style.Icon>}
{children}
</>
)}
</Style.InnerText>
</Style.Button>
);
};
자세히 살펴보면 Button
컴포넌트 안에 InnerText
란 태그로 감싸져 있고,
GTM
은 클릭한 대상을 dataLayer
에 등록하기 때문에
위 사진에서 span태그인 InnerText
를 추적하는 것으로 유추해 볼 수 있었다.
이제 이유를 알았으니, 해결해 보도록 하자.
첫번째로 InnerText
를 없애보려 했다.
하지만 이 방식은 Button
컴포넌트에서만 해결할 수 있는 방식이었고,
만약 children
이 태그가 감싸진 형태로 넘어오게 되면 똑같은 현상이 발생한다.
동일하게 아이콘을 클릭해도 이벤트는 잡히지 않을 것이다.
- 감싸고 있는
InnerText
컴포넌트를 제거 ❌
두번째로, 내부의 모든 컴포넌트에 id
를 등록하는 것이다.
생각만 해도 벌써 귀찮다..
만약에 추적해야 하는 id
아래에 태그들이 열 개 있으면 열 개를 전부 등록해 주어야 한다..
그리고 코드 또한 id
범벅이 될 것이다.
<button id="create">
<div id="create">
<div id="create">
...
...
...
</div>
</div>
</button>;
- 감싸고 있는
InnerText
컴포넌트를 제거 ❌- 내부의 모든 컴포넌트에
id
를 등록 ❌
마지막인 click을 했을 때 dataLayer에 직접 등록을 해주는 방식을 선택했다.
직접 등록해 주는 것은 단순하다.
클릭했을 때 실제로 GTM이 클릭 이벤트를 수집하는 방식을 흉내내어 넣어주면 될 것 같다.
//dataLayer에 직접 등록하는 함수
dataLayer.push({ event: 'gtm.click', 'gtm.elementId': '등록할 아이디' })
- 감싸고 있는
InnerText
컴포넌트를 제거 ❌- 내부의 모든 컴포넌트에
id
를 등록 ❌- 클릭 시,
dataLayer
에 직접 등록 🟢
그런데 어떻게..?
모든 onClick
이벤트에 dataLayer
에 push
할 수는 없다.
코드도 섞여서 별로 좋지 않고, 사실 귀찮은 게 제일 크다..
한 번에 한 곳에서 이 문제를 해결하고 싶었다.
그래서 우리 팀은 이벤트 버블링
을 통해 해당 문제를 해결하고자 했다.
모든 onClick
에 dataLayer.push()
함수를 넣어두기 보다는
어차피 이벤트 버블링이 발생하기 때문에,
가장 최상위 태그에 함수를 넣어두면 어디에서 클릭하든지 해당 함수가 발동될 것이다.
이제는 id
만 찾으면 된다.
HTMLElement
에는 parentElement
란 프로퍼티가 존재한다.
즉, 부모에 관한 정보는 parentElement
로 찾을 수 있다.
하지만 감싸고 있는 부모가 한겹이라는 보장이 없기에,
재귀함수
를 통해 가장 최상위 Element
인 app
까지 탐색을 진행하고,
그 전까지만 id를 탐색하도록 설정하였다.
//부모의 id를 탐색하는 함수
const findParentId = (target: HTMLElement, flag = true) => {
if (target?.id === 'app') {
//아이디가 아무 것도 없음
return;
}
if (target?.id !== '' && flag) {
//타겟에 아이디가 존재한다
return;
}
if (target?.id !== '') {
//부모에 아이디가 존재한다
dataLayer.push({ event: 'gtm.click', 'gtm.elementId': target?.id as string });
return;
}
findParentId(target.parentElement!, false);
};
아래와 같이 이제는 추적하고자 하는 id
를 추적할 수 있었다.
물론 id
를 추적하는 데에는 성공 했지만, 아쉬운 점이 한 둘이 아니다.
가장 크리티컬한 이슈는 원하는 버튼의 클릭 횟수를 정확하게 파악할 수는 있지만,
해당 로직과는 별도로 GTM
에서 클릭 이벤트를 수집하고 있기 때문에
전체 클릭 횟수는 2배로 잡히고 있었다. 🥲
두번째로 어디를 클릭하든 재귀함수를 돌아야 했기 때문에,
불필요한 작업이 추가되어 확실히 성능상에도 좋지 못할 것이다.
(물론 보통 depth는 깊어봤자 20-30개이기 때문에, 체감상 느껴지는 성능이슈는 발생하지 않았다)
이런 문제를 해결하는 데에 GTM
에서 제공하는 방법이 있는데 내가 못 찾은 것일 수도 있지만,
발견하지 못했고, 현재까지의 고민이 내가 할 수 있는 최대였다.