Thumbnail Image
React | 2024.03.30

Difference between Native and React Event

마우스 및 스크롤 관련 이벤트를 다루다가, 이해 되지 않는 현상을 맞닥뜨렸습니다. 이에 대해 좀 더 깊게 현상을 파악하고 문제를 정의하고자 하였습니다. 과정에서 정리된 Event에 대한 인사이트를 기록하여 공유하고자 합니다.

목차

1️⃣ 현상

2️⃣ Native DOM Event

3️⃣ React Event

4️⃣ 현상 다시보기

1️⃣ 현상

1...
2const parentRef = useRef(null);
3const childRef = useRef(null);
4
5useEffect(() => {
6 parentRef.current.addEventListener(
7 'click',
8 () => {
9 console.log('[Native DOM] parent clicked');
10 }
11 );
12 childRef.current.addEventListener(
13 'click',
14 (e) => {
15 // e.stopPropagation();
16 console.log('[Native DOM] child clicked');
17 }
18 );
19}, []);
20
21return (
22 <div
23 ref={parentRef}
24 onClick={() => {
25 console.log('[React DOM] parent clicked');
26 }}
27 >
28 <div
29 ref={childRef}
30 onClick={(e) => {
31 // e.stopPropagation();
32 console.log('[React DOM] child clicked');
33 }}
34 >
35 content
36 </div>
37 </div>
38);
39...

1.

click 이벤트 순서

1[Native DOM] child clicked
2[Native DOM] parent clicked
3[React DOM] child clicked
4[React DOM] parent clicked

1.

Native Child DOM에서 이벤트 전파를 막는다면,

1[Native DOM] child clicked

1.

React Child DOM에서 이벤트 전파를 막는다면,

1[Native DOM] child clicked
2[Native DOM] parent clicked
3[React DOM] child clicked

위 현상에 대해 특이점이 느껴집니다.

“React Event는 일련의 조건에 의해 Native Event보다 우선순위가 낮게 동작하나?”

라는 막연한 추측을 가지고 관련 개념들을 알아보고자 합니다.

2️⃣ Native DOM Event

DOM에서 발생하는 🔗Event라고 함은, Event 객체를 가리킵니다.

저희가 알다시피, 사용자의 액션 혹은 API들의 비동기적 작업 진행을 위해 생성됩니다. 또는, Event 객체를 활용하여 custom event를 attach하거나 dispatch 할 수 있습니다.

Event 객체의 property와 method들은 🔗문서에서 확인해 볼 수 있습니다.

DOM의 “Event”는 Layout 및 Paint 과정 이후, 각 요소의 크기와 위치를 계산된 값을 가지고 발생합니다. 위에서 언급된 Event 공식문서에서 소개되는 “Event 객체”가 생성되며 target 하는 DOM Node를 찾습니다.

Render Tree를 기반하여 캡쳐링 및 버블링을 진행하고, attach 되어있던 이벤트 핸들러 함수를 순서대로 호출합니다. (FYI, 해당 함수는 Main thread를 거쳐 Compositor thread를 통해 동작합니다. passive 속성을 true로 두었다면, Compositor thread 선상에서 작업을 진행할 수 있습니다)

이 과정을 통해 이벤트가 생성되고 이벤트 핸들러가 호출됩니다.

가장 중요한 점은, 이벤트 핸들러를 attach 하는 과정은 특정 DOM Node에 직접 등록한다는 점입니다. 이는 React Event와 주된 차이점을 가집니다.

이후 React Event를 살펴보기 전에 “synthetic event”에 대해서 짚고 가면 좋을 것 같습니다.

Event API의 Event() 생성자로 새로운 Event 객체를 생성할 수 있습니다. 이때, 생성된 이벤트는 브라우저가 생성하는 이벤트와 구분해서 합성 이벤트(synthetic event)라고 지칭합니다. 이렇게 Event() 생성자 및 CustomEvent() 메서드를 사용하여 직접 생성한 event를 🔗dispatch 할 수도 있습니다.

3️⃣ React Event

React는 Native DOM Element에서의 Event와는 다른 custom Event를 가지고 있습니다. 해당 객체는 🔗React의 소스 코드에서 BaseSyntheticEvent를 통해 구조를 확인해볼 수 있습니다.

1type BaseSyntheticEvent = {
2 isPersistent: () => boolean,
3 isPropagationStopped: () => boolean,
4 _dispatchInstances?: null | Array<Fiber | null> | Fiber,
5 _dispatchListeners?: null | Array<Function> | Function,
6 _targetInst: Fiber,
7 nativeEvent: Event,
8 target?: mixed,
9 relatedTarget?: mixed,
10 type: string,
11 currentTarget: null | EventTarget,
12};

React에서 핸들링하는 Event 객체는 nativeEvent를 내포하고, target, currentTarget 등 React에서 렌더링하는 Node 정보 등을 가지고 있습니다.

React의 Event 시스템은 개발자가 DOM Event를 더 편리하게 사용할 수 있도록 설계되어 있습니다. 다양한 브라우저에서 일관된 동작을 제공하여, 개발자는 동일한 코드로 Cross-Browsing에 대한 일관된 경험을 제공할 수 있게 되는 것이죠. 또한, Event 객체를 직접 생성함으로써 원하는 Event 처리를 효율적으로 최적화하여 관리할 수 있습니다.

이것이 시사하는 바는, React Event는 React 시스템에 의해 동작한다는 것입니다.

Instead, it will attach them to the root DOM container into which your React tree is rendered: - React

<2️⃣ Native DOM Event>에서 잠깐 언급된 부분입니다. React Event는 🔗React17 공식문서에 따르면, 이벤트 핸들러를 React tree가 렌더되는 root DOM container에 attach 한다는 사실을 알 수 있습니다. <1️⃣ 현상>에서 발견한 특이점 또한 설명될 실마리가 여기에서 보입니다.

React는 모든 Native Event 정보들을 모아두고, onClick(click), onPointerDown(pointerdown)과 같은 React 이벤트 핸들러 Property들을 매핑합니다. 이렇게 생성된 매핑 테이블은 이벤트가 발생할 때 적절한 핸들러 함수와 매칭해주는 역할을 합니다.

이후에 Virtual DOM에 해당하는 이벤트 핸들러들을 등록합니다. 이를 통해 아래와 같이 root DOM Element에 모든 이벤트 핸들러가 부착되어 있음을 알 수 있습니다.

이벤트가 감지되면 attach한 이벤트 리스너가 트리거되고, dispatchEvent() 함수를 호출하여 이벤트에 알맞은 동작들이 실행됩니다. dispatchEvent 함수는 어떤 동작을 할까요?

Event 객체의 정보를 확인하여, Fiber node 요소를 찾고, 해당 node부터 Root node까지 Fiber Tree를 순회합니다. 순회 과정에서 매칭되는 Event Property과 매칭되는 이벤트 리스너를 찾고, 해당 리스너에 해당하는 동작을 별도의 Queue에 저장합니다.

Root node까지 순회한다면, Queue 구조에 따라 순서대로 동작을 실행하여 이벤트 핸들링 함수를 호출하게 됩니다.

저희가 알던 Native Event와는 분명히 다른 동작 방식을 가집니다.

이제 다시 <1️⃣ 현상>을 바라본다면, 다른 시각으로 보이게 될 것으로 기대됩니다.

4️⃣ 현상 다시보기

“React Event는 Native Event보다 우선순위가 낮나?”

해당 문장은 틀린 문장입니다.

addEventListener로써 특정 DOM에 이벤트 리스너를 부착한다면, 이벤트가 발생할 때 해당 target 되는 Element에서 핸들링 함수가 호출됩니다.

하지만 React의 Event Listener를 사용한다면, 해당 이벤트는 Root Node에 리스너가 부착되어 동작하기 때문에, 해당 동작이 특정 DOM에 부착된 이벤트 리스너보다 후순위에 일어나게 됩니다.

만약, React Event Listener로써 Capturing을 발생하는 핸들링 함수를 선언한다면, 해당 함수는 내부 Node에 부착한 Native Event보다 앞서 호출되게 됩니다.

사소해보이지만, 이와 같은 개념이 없다면 혼동이 일어날 수 있었던 현상을 쉽게 맞닥뜨릴 수 있습니다.

자칫하면 올바르지 않은 문제 정의를 통해 부적절한 해결법을 도출할 수 있기 때문에, 현상을 정확히 이해하는 것이 중요합니다. 이와 같은 학습을 꾸준히 하고자 합니다.

감사합니다🙏

Ref

https://github.dev/facebook/react / BaseSyntheticEvent, dispatchEvent