본문 바로가기
WEB/깊게 공부하기

React가 이벤트를 관리하는 방식 - 1

by IT황구 2023. 6. 15.
728x90
반응형

올해가 가기 전 리액트를 소스코드 레벨에서 분석해보는게 목표였습니다.

목표를 간단하게 잡고 분석을 해보자 했지만, 생각보다 모르는게 너무 많더라구요. ㅎㅎ

거의 2~3주 동안 시간이 나면 계속 틈틈히 찾아봤던것 같습니다.

 

첫번째 주제는 React에서 이벤트를 어떻게 관리하는지에 대해서 자세하게 알아보겠습니다.

 

이 글을 읽고나면 아래 내용들을 코드로 직접 확인할 수 있습니다. (10개가 넘는데 다 쓰진 않겠습니다)

- 리액트에서 이벤트 버블링을 사용하지 않아도 되는 이유

- 리액트의 이벤트는 document가 아닌 rootElement에 등록된다는 사실

- 리액트에서 어떻게 이벤트 버블링을 통해 이벤트를 처리하는지?

- 어떻게 특정 함수만 찾아서 영리하게 실행하는지?

- onClick 함수를 onClick(e,a,b)로 썼을때 왜 항상 e만 bind가 되고, a,b 는 undefined로 나오는지?

- react는 'click'이 아닌 onClick을 사용하는데 어떻게 해석하고 이해하는지?

 

분석은 React 가장 최신버전인 v18.2.0 을 기준으로 진행합니다.

 

react의 이벤트는 언제 만들어지는가?

 

 

저는 onClick에 설정한 함수가 어떻게 등록되고 실행되는지 궁금했습니다. 리액트도 결국 js 입니다. 

어디선가  node.addEventListener('click', listener) 를 통해서 등록을 해야합니다. 그 과정을 알아보겠습니다.

 

 

react-dom.js 가 실행되면 rootElement를 만들기 전 native event(click), react에서 사용하는 이벤트명(onClick)을 생성하고 매핑하는 과정이 있습니다. react-dom이 실행되자마자 함수를 실행시킵니다. DOMPluginEventSystem.js 에서 확인할 수 있습니다.

 

  • React는 이벤트의 종류에 따라서 eventPlugin으로 구분을 했습니다. 5번의 registerEvents() 를 거치고나면 다음과 같은 값들을 미리 얻을 수 있습니다.
    • topLevelEventsToReactName: 'click' -> 'onClick' 처럼 native event 이름을 react name으로 변환시켜주는 Map 
    • allNativeEvents : 모든 native event들을 저장하고 있는 Set 
    • registrationNameDependencies : 리액트에서 실제로 사용하는 이벤트 이름 onClick, onClickCapture 들이 어떤 이벤트와 매핑되어 있는지 알 수 있는 object 

 

SimpleEventPlugin code를 보고 실제 값을 확인 해 보겠습니다.

 

  • 여기서 topLevelEventsToReactName, 몇몇 이벤트 리스트들이 보입니다. 리액트에서는 'click' 대신 'onClick' 을 사용하는데, 변환해주는 Map을 바로 이 시점에 생성하게 됩니다. (나중에 코드에서 사용처를 확인할 수 있습니다)

 

이제 SimpleEventPlugin 에서 호출하는 registerEvents() 를 확인해보겠습니다.

 

  • registerSimpleEvents는 여러 이벤트를 등록합니다. click을 예로 들면 registerSimpleEvent('click', 'onClick') 을 호출합니다. 
  • registerTwoPhase 의 뜻은 이벤트 버블링, 캡처링 케이스를 모두 고려하겠다는 의미입니다.

 

registerTwoPhase 함수로 이동하면 2가지 중요한 내용을 알 수 있습니다.

  • 이곳에서 native event 및 react event를 실제로 매핑합니다.
  • 캡처링을 사용하려면 onClickCapture 처럼 따로 명시적으로 써주어야 한다는 점 입니다. onClick 은 캡처링 단계에 감지되지 않습니다.

 

 

이벤트 종류별로 registerEvents를 마치게 되면 다음과 같은 Map, Set, Object를 얻게 됩니다. 

참고로 캡처링이 있는 이벤트는 Capture가 따로 붙은것을 알 수 있습니다.

 

 

이벤트를 실제로 등록하기 전 매핑작업을 하는 함수들과 그 결과를 확인했습니다.

아직 react-dom은 어떤 엘리먼트도 생성하지 않았습니다. 그저 기초단계일 뿐입니다.

 

 

 

만들어진 Event들은 언제 등록이 되는가?

 

 

event를 매핑하는 과정은 root element가 만들어지기 전에 일어난다는것을 확인했습니다.

 

  • 실제로 이벤트를 등록(addEventListener) 하는 과정은 createRoot를 처리하는 과정에서 일어나게 됩니다. 해당 함수를 확인해보겠습니다.

 

  • createRoot 함수에서 rootFiber (root element에 대한 정보를 저장한 객체) 를 생성하고나서  listenToAllSupportedEvents 를 호출합니다. 여기서 주목해야 할 점은 rootContainerElement 를 인자로 넘긴다는 것입니다.

 

  • listenToAllSupportedEvents 에서 익숙한 변수명이 보입니다. allNativeEvents 를 순회하면서 listenToNativeEvent 를 호출합니다.
  • allNativeEvents는 위의 사진에서 봤던 native event들을 가지고있던 Set 입니다.
    • cancel, click, close 이런 값들을 순회하면서 listenToNativeEvent 를 호출하고 있습니다.
    • 인자는 ('click', true(캡처링) / false(버블링), rootContainer) 가 넘어갑니다.

 

이어서 봐야할 곳은 listenToNativeEvent 함수에서 호출한 addTrappedEventListener 입니다. 이 함수에서는 2가지를 알 수 있습니다.

  • 이벤트를 등록하는 rootElement.addEventListener(type, listener) 의 구조에서 listener(함수) 는 결국 dispatchEvent 와 관련이 있는 함수라는것을 알 수 있습니다.
  • listener는 이벤트의 종류에 따라 매핑되는 함수가 달라집니다. 하지만 최종적으로 dispatchEvent 를 내부적으로 호출합니다. (세부 구현은 들어갈수록 내용이 많아져서 skip 합니다)

 

  • 이 함수에서 이벤트를 처리할때 사용할 함수를 bind 해서 리턴해주는것을 확인할 수 있습니다.
    • 이때 사용된 argument는 다음과 같습니다
(
  domEventName : 'click',
  eventSystemFlag : IS_CAPTURE_PHASE or IS_NON_DELEGATED...
  targetContainer: 'rootElement' (rootFiber)
)

 

addTrappedListner에서 createEventListenerWrapperWithPriority 를 통해서 listner를 얻었습니다.

이제 이 listener를 이용해서 실제로 이벤트를 등록하는 단계가 있습니다.

 

  • 이벤트의 phase에 따라서 등록하는 함수를 나눠놨지만 예시로 addEventBubbleListener 를 보겠습니다

 

  • 여기서 실제로 addEventListener가 일어나는것을 확인할 수 있습니다.
  • 또 하나 다른점은 우리가 평소에 사용하던 document.addEventListener가 아닌 target으로 되어있다는 점입니다. 또한 이 target은 rootContainer 입니다. 
 

React v17.0 – React Blog

This blog site has been archived. Go to react.dev/blog to see the recent posts. Today, we are releasing React 17! We’ve written at length about the role of the React 17 release and the changes it contains in the React 17 RC blog post. This post is a brie

legacy.reactjs.org

 

이벤트를 실제로 등록하는 과정을 거치고나니 <div onClick={onClick}> Hello </div> 같은 엘리먼트의 onClick 함수들을 특정 엘리먼트에 직접 달거나 하지 않는다는것을 알 수 있었습니다.

 

정리

 

1. react-dom js 파일이 실행될 때, 미리 native event name을 등록하고, native event name을 react event name으로 변환하는 Map 등을 생성하는 과정이 있습니다.

 

2. rootContainer를 생성하기 위해 createRoot를 호출하면, 미리 등록한 native event list를 활용하여 이벤트를 등록합니다. 이 과정에서 이벤트의 버블링, 캡처링 단계를 모두 고려하여, document가 아닌 rootElement에 이벤트를 등록합니다.

 

 

 

내가 만든 이벤트 핸들러들은 하나도 등록이 되지 않았는데, 이 부분은 어떻게 실행시킬까요?

 

이 부분은 2편에서 알아보겠습니다.

 

한번에 다 쓰게되면 정보가 너무 많을것 같습니다.

 

감사합니다.

 

 

Reference

- https://github.com/facebook/react/tree/v18.2.0

728x90
반응형