개발새발 로그

React에서 이벤트는 뭐가 이리 달라? 본문

React

React에서 이벤트는 뭐가 이리 달라?

이즈흐 2024. 4. 21. 05:12

 

리액트에서는 이벤트 객체가 아닌 합성 이벤트라는 객체를 사용한다고 한다.

리액트에서 이벤트를 처리할 때 이벤트 위임으로 처리한다고 한다.

 

이렇게 리액트에서는 이벤트를 기존과 다른 방식으로 처리하는 것 같다.

이를 공부하면서 학습한 내용을 정리해보려고 한다.

 

 

📖합성이벤트

🤔Event에 offsetX가 없다고..?

 예전에 프로젝트를 진행하면서 Event객체를 사용할 때 문제가 하나 있었다.

받아온 Event 객체에 내가 원하는 속성이 없다고 뜨는 문제였는데 

그때는 그냥 "아~ 그렇구나" 하고 넘기고 해결했었다.

 

오늘은 그 문제를 한번 다시 갖고 와서 깊게 파고들려고 한다.

 

리액트에서 아래와 같이 작성하면 타입스크립트 오류가 뜬다.

  function handleClick(e: MouseEvent<HTMLButtonElement>) {
    console.log(e.offsetX)
  }

위처럼 offsetX가 없다고 뜬다.

실제로 실행해 보면 undefined가 뜬다.

 

왜 event 객체에 offsetX가 없다고 뜨는 걸까?

 

MouseEvent를 거슬러 올라가 보자..!

우선 타입스크립트에서 오류를 발생한 MouseEvent를 보자

일단 offsetX는 보이지 않는다.

근데 MouseEventUIEvent를 상속받고 있다.

 

그럼 UIEvent를 살펴보자

별다른 내용이 없다.

하지만 UIEventSyntheticEvent라는 것을 상속받고 있다.

 

그럼 SynheticEvent을 살펴보자

또 별다른 내용은 없고 BaseSyntheticEvent라는 것을 상속받고 있다.

 

그럼 BaseSyntheticEvent를 살펴보자

여기에는 우리가 흔히 아는 메서드나 속성이 존재한다.

여기서 navtiveEvent를 주목해야 한다.

 

nativeEvent는 E라는 것을 받고 있는데

E가 무엇일까?

 

다시 MouseEvent로 돌아오자.

MouseEvent에서 참조하고 있는 NativeMouseEvent 브라우저의 마우스 이벤트다. MDN에 설명되어 있음

MouseEvent는 UIEvent를 상속하고, UIEvent는 Event를 상속한다 나와있다.

 

그런데 리액트 타입에는 UIEvent에서 바로 Event 타입으로 가는 것이 아닌, SyntheticEvent가 있다는 것이다.

그리고 그 위에는 BaseSyntheticEvent 가 있다는 것이다.

 

그리고 E라는 것을 따라가면 Event라는 타입을 가리키고 있다.

이게 Event 객체..?

비어있는 이유는 React 프로젝트가 DOM라이브러리를 포함하지 않는 경우에도 컴파일할 수 있게 하도록 하기 위해서라고 한다. (DOM라이브러리가 포함되지 않는 경우 => 예를 들어 React Native 애플리케이션)
Event가 실제 Event객체인지 비어있는 이유가 정확히 맞는 건지 등 이 부분은 다시 자세히 찾아봐야겠다..

 

 

아무튼 위 코드를 정리해 보면 아래와 같은 그림으로 볼 수 있겠다.

 

SyntheticEvent - 합성이벤트

리액트의 SyntheticEvent는 브라우저의 네이티브 이벤트를 감싸는 리액트의 이벤트 래퍼다.

위에서 확인했던 e전부 브라우저가 아닌 리액트의 이벤트 객체다.

SyntheticEvent는 기본적으로 DOM 이벤트 표준을 따르지만,

일부 브라우저마다 다르게 동작하는 기능을 일관되게 동작하게 해 준다.

 

예를 들어, 리액트의 onMouseLeave mouseout라는 네이티브 이벤트를 가리킨다.

특정 기능은 퍼블릭 API에 매핑되지 않았으며, 미래에 언제든지 변경될 수 있다.

그러니 만약 기본 브라우저 이벤트가 필요하다면, e.nativeEvent를 참조해야 한다.

 

BaseSyntheticEvent 인터페이스는 기본적으로 표준 Event에서 구현되었으며, React event object에서 확인 가능하다.

위에서 말했듯이 리액트만의 속성 중 하나인 navtiveEvent가 원래의 브라우저 이벤트 객체를 반환한다.

 

그래서 e.nativeEvent에서 offsetX와 같은 속성을 사용할 수 있다.

 

 

🤔왜 합성이벤트일까?

리액트에서 사용하는 이벤트 객체는 브라우저의 이벤트 객체와는 다른, 리액트만의 객체이다.

 

브라우저마다 이벤트 이름부터 이벤트 종류이벤트가 처리되는 방식이 다르다고 하는데

이를 동일하게 처리하기 위해 리액트는 브라우저의 이벤트 객체를 래핑 한 합성 이벤트를 사용크로스 브라우징 문제를 해결했다고 한다.

 

합성이벤트가 브라우저간의 일관된 이벤트 처리를 제공한다는 게 잘 안 와닿아서 예시를 가져와봤다.

기존의 JavaScript 이벤트 처리 방식

다른 브라우저들: event.target을 사용하여 이벤트가 발생한 요소를 참조

Internet Explorer 8 이하: event.srcElement을 사용하여 이벤트가 발생한 요소를 참조

function handleClick(event) {
  // 브라우저 호환성 처리
  var target = event.target || event.srcElement;
  console.log(target);
}

React에서의 SyntheticEvent를 사용한 처리

React에서는 이벤트 핸들러 내에서 event.target을 직접 사용할 수 있다.

React의 SyntheticEvent 시스템이 브라우저 간의 차이를 내부적으로 처리해 준다.

function handleClick(event) {
  console.log(event.target); // React가 브라우저 호환성을 처리
}

 

 

 

 

 

📖이벤트 위임

🤔내 이벤트 어디 갔어?

button에 onClick 이벤트를 등록하고 이벤트 리스너를 보면 아래와 같은 함수가 등록된 것을 볼 수 있다.

나는 분명 handleClick과 같은 이름으로 등록을 했는데 noop이라는 이름의 함수가 적혀있다.

그래서 noop 함수를 살펴보면 이름과 같이(no operation) 아무런 로직도 수행하지 않는 것을 볼 수 있다.

 

리액트는 이벤트 핸들러를 해당 이벤트 핸들러를 추가한 각각의 DOM 요소에 부탁하는 것이 아니라, 이벤트 타입 당 하나의 핸들러를 루트에 부착한다.

 

이를 이벤트 위임이라고 한다.

이벤트 위임이란 캡처, 타깃, 버블링을 활용해 이벤트를 상위 컴포넌트에만 붙이는 것을 의미한다.

 

여기서 React 17 이전에는 document에 부착하고, 17 버전부터는 리액트 컴포넌트 최상단 트리, 즉 root 요소에 부착했다.
이유를 간단하게 말하면 점진적인 업그레디 으 지원과 다른 바닐라 자바스크립트 코드 또는 jQuery 등이 혼재돼 있는 경우 혼란을 방지하기 위해서 이다.
만약 다양한 버전의 리액트가 한 서비스에 공존할 때 이벤트를 이벤트 위임으로 document에 부착 시
이벤트들은 모두 최상위에 등록되어 e.stopPropagation()이 정상적으로 동작하지 않을 수 있다.

하지만 각각 버전의 해당하는 root에 등록하면 e.stopPropagation()은 일반적인 DOM에 더 가깝게 작동하여 원하는 결과(ex. 외부에 이벤트 전파 막기)를 기대할 수 있게 된다.
-> 각 버전의 리액트는 별도의 루트를 가지기 때문이다.
[ 예를 들어, 한 페이지 내에서 리액트 16으로 만들어진 컴포넌트와 리액트 17로 만들어진 컴포넌트가 공존한다고 가정해 보겠습니다. 이 경우 두 컴포넌트 각각에 대해 ReactDOM.render() 함수(또는 리액트 18 이상에서는 createRoot() 함수)를 호출하여 별도의 루트를 생성해야 합니다. 이렇게 하면 각 버전의 리액트가 각각의 컴포넌트 트리를 독립적으로 관리할 수 있습니다. ]

 

 

🤔그럼 내가 등록한 이벤트는 언제 루트로 붙여지는 걸까?

일단 이벤트 위임 그림을 보자

그림에서처럼 root에 붙여진다고 표현하고 있다.

 

이 그림이 틀린 건 아니다만, 약간의 오해의 소지가 있는 그림이라고 한다.

여기서 root에 붙이는 것은 하위 컴포넌트에 있는 수도 없이 많은 이벤트 핸들러들이 아니다.

 

예를 들어, <button onClick={() => console.log('btn')}> 이런 컴포넌트가 있다 하더라도, root에  rootNode.addEventListener('click', () => console.log('btn'))처럼 붙이는 게 아니라는 말이다.

 

여기서는 Native Event를 Listen 하고, 그 이벤트에 따라 ‘알맞은 타깃의 핸들러를 찾아서 실행시키는’ 핸들러를 포함한 Lisnener를 root에 등록한다.로

 

코드를 보자

깃허브 링크

// https://github.com/facebook/react/blob/v18.2.0/packages/react-dom/src/client/ReactDOM.js
export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
  // 생략 ...
  
  return createRootImpl(container, options); //createRootImpl함수는 ReactDomRoot.js에서 가져옴
}


//https://github.com/facebook/react/blob/main/packages/react-dom/src/client/ReactDOMRoot.js
//creatRootImpl 함수의 일부분
  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container;
  listenToAllSupportedEvents(rootContainerElement);

  return new ReactDOMRoot(root); // 루트를 만들기 전에 listenToAllSupportedEvents 수행


// ...


// https://github.com/facebook/react/blob/v18.2.0/packages/react-dom/src/events/DOMPluginEventSystem.js
const listeningMarker = '_reactListening' + Math.random().toString(36).slice(2);

export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
  // rootContainerElement, 즉 id가 root인 엘리먼트에 이벤트 리스너가 부착되어 있지 않은 경우 실행한다.
  if (!(rootContainerElement: any)[listeningMarker]) {
    // 내부에서 이벤트가 부착되었다는 flag를 설정하여, 이후에 다시 실행되지 않게 동작. 따라서 첫 렌더링에만 실행된다.
    rootContainerElement[listeningMarker] = true;
    // 모든 이벤트들에 대해 이벤트 리스너를 부착한다.
    allNativeEvents.forEach(domEventName => {
      if (domEventName !== 'selectionchange') {
        if (!nonDelegatedEvents.has(domEventName)) {
          listenToNativeEvent(domEventName, false, rootContainerElement);
        }
        listenToNativeEvent(domEventName, true, rootContainerElement);
      }
    });
    // ...
  }
}

reactRoot에서 ReactDOMRoot가 만들어지기 직전에 listenToAllSupportedEvents를 호출하고 있다.

 

해당 메서드를 자세히 보면, allNativeEvents.forEach 를 통해 모든 종류의 Native Event를 root에 붙이는 작업을 진행한다. (실제 클릭이라는 Native Event는 어떻게 처리할까? 부분에 설명했습니다.)

 

listenToNativeEvent는 내부에서 rootContainerElement 에다가 addEventListener를 이용해 Listener를 붙이는 역할을 한다.

 

여기서 nonDelegatedEvents에서는

버블링이 안 되는 이벤트의 경우에는 캡처링 상태에 대한 Listener만 붙이고,

아니면 버블링이 되는 경우에는 버블링에 대한 Listener, 캡처링에 대한 Listener 각각 하나씩 붙이는 작업을 한다.

 

이 Listener를 붙이는 과정은 createRoot라는 이름에서 알 수 있듯 처음 root가 만들어질 때, 즉 앱이 켜질 때 진행된다.

 

그럼 listenToNativeEvent 함수에서 실제 이벤트 리스너를 등록하는 것 같다.

내부를 확인해 보자

깃허브 링크

// https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js

// 실제 브라우저 이벤트를 연결한다
export function listenToNativeEvent(
  domEventName: DOMEventName,
  isCapturePhaseListener: boolean,
  target: EventTarget,
): void {
  // ...
  let eventSystemFlags = 0; // 기본 값이 0인 것을 주목하자
  if (isCapturePhaseListener) {
    eventSystemFlags |= IS_CAPTURE_PHASE; // 캡쳐링을 한다면 4로 할당
  }
  addTrappedEventListener(
    target,
    domEventName,
    eventSystemFlags,
    isCapturePhaseListener,
  );
}

function addTrappedEventListener(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  isCapturePhaseListener: boolean,
  isDeferredListenerForLegacyFBSupport?: boolean,
) {
  // 우선순위에 따라 이벤트 리스너를 생성한다.
   let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
  );

  // ...
  let unsubscribeListener;

 // 실제 이벤트를 등록하러 가야한다.
  if (isCapturePhaseListener) {
    // ...
    unsubscribeListener = addEventCaptureListener(
        targetContainer,
        domEventName,
        listener,
      );
  } else {
    // ...
    unsubscribeListener = addEventBubbleListener(
        targetContainer,
        domEventName,
        listener,
      );
  }
}

그리고 아래와 같은 addEventListner를 수행하고 있는 코드에 도달하게 된다.

깃허브 링크

// https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/events/EventListener.js
export function addEventBubbleListener(
  target: EventTarget,
  eventType: string,
  listener: Function,
): Function {
  target.addEventListener(eventType, listener, false);
  return listener;
}

export function addEventCaptureListener(
  target: EventTarget,
  eventType: string,
  listener: Function,
): Function {
  target.addEventListener(eventType, listener, true);
  return listener;
}

따라서 사용자에게 화면이 보일 때는 이미 이벤트 핸들러들이 다 부착된 상태이기에 클릭, 작성 등의 이벤트들이 동작한다.

 

그림으로 정리하면 아래와 같다.

createRoot도 ReactDoomRoot.js에 있는 함수를 쓰고있지만 생략했다.

 

실제로 리액트 프로젝트를 실행하고 root부분의 이벤트 리스너를 보면 모든 이벤트가 등록되어 있는 것을 볼 수 있다.

 

 

 

🤔실제 클릭이라는 Native Event는 어떻게 처리할까?

그럼 다시 돌아가서 등록된 NativeEvent를 어떻게 인식하는 걸까?

일단 DOMPluginEventSystem.js라 파일에서(위에서 listenToNativeEvent를 수행한 파일) 전역에 실행되는 코드가 있다.

깃허브 링크

여기 있는 registerEvents()가 이제 click을 onClick 같은 React 식 명명법으로 변환할 수 있도록 Map에 매핑해 주는 역할을 한다.

 

이 중에 예를 들어서 SimpleEventPluginregisterEvents에 들어가 보자.

깃허브 링크

위처럼 SimpleEventPlugin.js파일에서는 DOMEventProperties에서 가져온 registerSimpleEventsregisterEvents()라는 이름으로 내보내고 있다.

 

그럼 DOMEventProperties에서 주요 코드를 확인해 보자.

깃허브 링크

// https://github.com/facebook/react/blob/v18.2.0/packages/react-dom/src/events/DOMEventProperties.js
export const topLevelEventsToReactNames: Map<
  DOMEventName,
  string | null,
> = new Map();

const simpleEventPluginEvents = [
  'abort',
  'auxClick',
  'cancel',
  'canPlay',
  'canPlayThrough',
  'click',
  // ... 중략 ...
  'touchMove',
  'waiting',
  'wheel',
];

// ... 중략 ...

// 이 함수는 각 DOM 이벤트를 React 이벤트 이름으로 매핑하고, 이벤트를 등록하는 역할을 합니다.
function registerSimpleEvent(domEventName, reactName) {
  topLevelEventsToReactNames.set(domEventName, reactName); // Map에 DOM 이벤트 이름과 React 이벤트 이름을 매핑합니다.
  registerTwoPhaseEvent(reactName, [domEventName]); // EventRegistry에서 정의되어있음
}

// 이 함수는 위에서 정의한 이벤트들을 순회하며, 각 이벤트를 등록하는 과정을 수행합니다.
export function registerSimpleEvents() {
  for (let i = 0; i < simpleEventPluginEvents.length; i++) {
    const eventName = ((simpleEventPluginEvents[i]: any): string);
    const domEventName = ((eventName.toLowerCase(): any): DOMEventName);
    const capitalizedEvent = eventName[0].toUpperCase() + eventName.slice(1);
    registerSimpleEvent(domEventName, 'on' + capitalizedEvent); // 위에서 정의한 함수 실행
  }
  // Special cases where event names don't match.
  // ... 후략
}

 

그리고 registerEvents()에서는 click 같은 애를 onClick으로 둔갑해서 topLevelEventsToReactNames라는 곳에 저장해주고 있다.

 

그리고 EventRegistry 파일에 있는 registerTwoPhaseEvent를 호출해주고 있다.

 

EventRegistry파일에서는 무엇을 하는 것일까?

주요 코드만 보자

깃허브 링크

// https://github.com/facebook/react/blob/v18.2.0/packages/react-dom/src/events/EventRegistry.js
// 이 함수는 두 단계 이벤트(버블링과 캡처링 단계)를 등록합니다.
export function registerTwoPhaseEvent(
  registrationName: string,
  dependencies: Array<DOMEventName>,
): void {
  registerDirectEvent(registrationName, dependencies); // 직접 이벤트를 등록합니다.
  registerDirectEvent(registrationName + 'Capture', dependencies); // 캡처링 단계 이벤트를 등록합니다.
}

// 이 함수는 이벤트의 종속성을 등록하고, 모든 네이티브 이벤트를 관리하는 역할을 합니다.
export function registerDirectEvent(
  registrationName: string,
  dependencies: Array<DOMEventName>,
) {
  registrationNameDependencies[registrationName] = dependencies; // 이벤트 이름에 대한 종속성을 등록합니다.

  for (let i = 0; i < dependencies.length; i++) {
    allNativeEvents.add(dependencies[i]); // 네이티브 이벤트 집합에 종속성을 추가합니다.
  }
}

여기서는 이벤트 버블링, 이벤트 캡처링을 나누어서 두 번 등록해주고 있다.

클릭으로 예를 들자면 onClick , onClickCaptureonClickCapture를['click']이라는 값의 dependencies 와 함께 넘기는 상황이라고 보면 된다.

 

근데 코드 아래에서 allNativeEvents에 dependencies 값을 넣어주는 걸 확인할 수 있다.

 allNativeEvents 가 위에서 처음 createRoot 할 때 나왔던 함수다!

 allNativeEvents.forEach(domEventName => { ... }

그리고 위 allNavtiveEvents는 아래와 같이 갖고 오고 있었다.

ReactDOM.js

 

allNativeEvents는 EventRegistry파일에서 아래와 같이 전역에 선언되어 있다.

EventRegistry.js

 

이를 통해

앱이 실행될 때 미리 기록해 둔 수도 없이 많은 이벤트들을 여기서 저장해 두고, root가 만들어질 때 그 정보를 가지고 Event Listener를 붙이는 것을 알 수 있다.

 

 

위 내용을 그림으로 정리해 봤다.

 

 

그럼 마지막 allNativeEvents에 add 한 이벤트들을 아까 위에서 forEach로 돌면서 모두 root에 이벤트 리스너로 등록하는 것이다.

 


 

 

그러면 실제 이벤트를 실행한다면 어떤 일이 일어날까?

위 코드들로 이벤트가 root에 이벤트 리스너 함수(addEventListener)가 부착된다는 걸 보았다.

그렇다면 반대로 button을 클릭할 때, root에 부착된 이벤트가 어떻게 실행되는 걸까?

 

버튼을 클릭할 경우, 이벤트 버블링이 일어나면서 DOM 트리를 통해 상위 요소로 버블링 되며 최종적으로 root 요소에 도달한다.

그리고 바인딩된 이벤트를 찾아 실행하는데 코드로 살펴보자.

 

아까 위에서 우선순위로 이벤트 리스너를 바인딩하는 함수(createEventListenerWrapperWithPriority())에서 리스너에 이벤트를 실행하는 dispatch 함수를 같이 바인딩해둔다.

깃허브 링크

// https://github.com/facebook/react/blob/v18.2.0/packages/react-dom/src/events/ReactDOMEventListener.js
export function createEventListenerWrapperWithPriority(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
): Function {
  const eventPriority = getEventPriority(domEventName);
  let listenerWrapper;
  switch (eventPriority) {
    case DiscreteEventPriority:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    case ContinuousEventPriority:
      listenerWrapper = dispatchContinuousEvent;
      break;
    case DefaultEventPriority:
    default:
      listenerWrapper = dispatchEvent; //요기에서 dispatchEvent를 넣고 있다.
      break;
  }
  return listenerWrapper.bind( // bind 중!
    null,
    domEventName,
    eventSystemFlags,
    targetContainer,
  );
}

createEventListenerWrapperWithPriority를 보면 dispatch~Event라는 메서드를 전달해 줌을 확인할 수 있다.

그렇다면 사용자에 의해 Native Event가 발생될 때면, 이 dispatchEvent 가 호출된다고 생각하면 되겠다.

 

 dispatchEvent를 파고들어 내려가보면, 우리가 원하는 부분을 찾을 수 있었다.

깃허브 링크

// https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js#L547
export function dispatchEventForPluginEventSystem(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  // ...
  if (targetInst !== null) {
    // rootContainer를 찾기 위해 타겟에서 계속해서 위로 올라가며 탐색한다
    let node: null | Fiber = targetInst;
    mainLoop: while (true) {
      // ...
    }
  }
  // ...
  batchedUpdates(() =>
    dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      ancestorInst,
      targetContainer,
    ),
  );
}

function dispatchEventsForPlugins() {
  // ...
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
}

function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: DOMEventName,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: null | EventTarget,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
) {
  // 기본적으로 해당 플러그인으로 이벤트 수행
  SimpleEventPlugin.extractEvents(...)
  const shouldProcessPolyfillPlugins = (eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0;
  if (shouldProcessPolyfillPlugins) {
    // 폴리필 여부를 판단한 후, 상황에 맞는 이벤트를 실행
  }
}

 

 

위 코드는 일부분만 갖고 왔다.

DOMPluginEventSystem.js에서 많은 과정이 있는데 일단 이를 요약하자면 아래와 같다.

  • DOM에서 Native Event가 발생했음을 React가 root에 붙여둔 이벤트 리스너가 인지한다.
  • 해당 이벤트 리스너에 붙여둔 dispatchEvent 가 발생한다.
  • 받아온 Naitive Event를 분류해서 적절한 SyntheticEvent로 추출하고, Target에 대한 핸들러를 뽑아서 dispatchQueue에 넣는다.
  • dispatchQueue에 들어있는 이벤드 핸들러들을 순서대로 쭉 실행한다.

아직 이해하지 못한 부분들이 많아 여기까지 정리해 봤다.

더 쉽게 이해할 수 있도록 그림으로도 정리해보려고 한다.

이 포스팅은 추후 수정될 것이다..!

 

 

 

📘마무리하며...

사실 SyntheticEvent로 변환하는 부분과 실제 이벤트가 발생하는 부분도 자세히 작성하면서, 내가 이해한 게 맞는지 확인하고 싶었지만 포스팅 내용에서 멀어지는 것 같아서 일단 요약만 갖고 왔다.

 

이렇게 자료를 찾고 읽으면서 리액트에서는 이벤트가 어떤 식으로 관리되고 동작하는지 자세히는 아니더라도 조금은 이해할 수 있게 되었다.

 

 

 

 

 

참고 링크

https://medium.com/hcleedev/web-react%EC%9D%98-event-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%82%B4%EB%B6%80-%EA%B5%AC%ED%98%84-%EC%9E%90%EC%84%B8%ED%9E%88-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-react-v18-2-0-39d59ab45bec

 

Web: React의 Event 시스템 내부 구현 자세히 알아보기 (React v18.2.0)

React 코드를 뜯어 보며 Event 시스템이 어떻게 되어있는지 자세히 알아보자

medium.com

https://velog.io/@ehdgus8054/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%86%8D%EC%84%B1-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0

 

이벤트 속성 이해하기

타입스크립트로 리액트의 이벤트객체 및 가상DOM의 이벤트 속성을 알아보자.

velog.io

https://velog.io/@tnghgks/React-React-%EC%97%90%EC%84%9C%EB%8A%94-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%A0%EA%B9%8C

 

[React] React 에서는 이벤트를 어떻게 처리할까 ?

Synthetic Event 키워드를 발견했을때 낯익음과 낯설음이 공존했다. React에서 버튼을 클릭하는 이벤트를 만들었을때 넘어오는 객체를 콘솔로 찍어보면 나왔던 객체였다. 궁금해서 공부하고 정리한

velog.io

https://medium.com/crossplatformkorea/react-%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%A5%BC-%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90-06-%ED%95%A9%EC%84%B1-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EC%99%80-event-pooling-6b4a0801c9b9

 

[React] 리액트를 처음부터 배워보자. — 06. 합성 이벤트와 Event Pooling

이 글을 남기는 계기는 Dan Ambramov의 블로그 Overreacted를 보며 React.js에 대해 더 깊이 공부할 필요성을 느꼈기 때문이다.

medium.com

https://jeongki.dev/post/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4-55-56%EC%9D%BC%EC%B0%A8-til-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%95%A9%EC%84%B1-%EC%9D%B4%EB%B2%A4%ED%8A%B8

 

[데브코스] 55, 56일차 TIL (리액트 합성 이벤트)

리액트의 합성 이벤트

jeongki.dev

https://www.howdy-mj.me/react/event-handling-in-react

 

React는 이벤트를 어떻게 처리할까?

회사에서 리액트의 js 컴포넌트를 tsx로 전환하는 과정에서 이벤트들에 대한 타입을 어떻게 선언해야 좋을지 궁금했다. 버튼에 대한 onClick 이벤트가 있다면 단순하게 `MouseEvent`로 작성했지만, 생

www.howdy-mj.me

 

 

강사님께 질문했던 내용인데

리액트에서는 onClick으로 이벤트를 등록하면 Root에 모이게 된다고 하셨는데제가 이 부분을 정확히 이해했는지 알고싶어서 질문 드립니다!
보통은 자바스크립트에서는 각 요소마다 addEventListener를 여러 번 호출하면 메모리 측면에서 비효율적이고 성능이 떨어질 수 있다고 알고 있습니다. 그래서 이벤트 위임을 사용하는 것으로 이해했습니다.
 
현재 강의에서는 각 요소마다 onclick 이벤트를 등록하셨는데리액트에서는 Root에서 중앙통제하기 때문에 상관없다는 것으로 인지했습니다.

1. 제가 알기로는 이벤트들을 모아다가 root에 addEventListener를 하는 것으로 알고 있는데 이게 맞을까요?
2. 그러면 리액트에서는 굳이 이벤트 위임을 사용할 필요가 없을까요? 예를 들어 onClick을 여러 요소에 등록하지 않고 상위 요소 하나에만 등록하는 것처럼 말이죠..!

답변으로는

둘 다 맞습니다! 

하시고 링크도 주셨다.

아래 링크를 보니 간단하게 이해가 가능했다.

https://medium.com/@agrawalsaurabh026/how-react-handles-events-and-utilizes-event-delegation-fb9f147cb650

 

How React Handles Events and Utilizes Event Delegation

React, a popular JavaScript library for building user interfaces, provides a robust event handling system that abstracts away browser…

medium.com

 

728x90
반응형
LIST