๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ‘จ‍๐Ÿ’ป web.dev/fe

React ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฐฉ์‹๊ณผ SyntheticEvent

by HandHand 2022. 10. 17.

 

React ๋Š” W3C ๋ช…์„ธ์— ๋”ฐ๋ผ ํ•ฉ์„ฑ ์ด๋ฒคํŠธ๋ฅผ ์ •์˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์— ๋Œ€ํ•ด ๊ฑฑ์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

ํ•ฉ์„ฑ ์ด๋ฒคํŠธ๋Š” Synthetic Event ๊ฐ์ฒด๋กœ ์ •์˜๋˜๋ฉฐ ๋ธŒ๋ผ์šฐ์ € ๊ณ ์œ  ์ด๋ฒคํŠธ์™€ ์™„์ „ํžˆ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

 

์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” React ์˜ ๋‚ด๋ถ€ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ์˜ ๋™์ž‘ ๋ฐฉ์‹์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜€

 

๐Ÿ“Œ SyntheticEvent ๋ž˜ํผ ๊ฐ์ฒด

React ์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ง•์˜ ์ผํ™˜์œผ๋กœ

๋ชจ๋“  ํ”Œ๋žซํผ์—์„œ ๋™์ผํ•œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ๊ตฌํ˜„๋œ ์ธํ„ฐํŽ˜์ด์Šค์ธSyntheticEvent ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ด๋Š” stopPropagation ๊ณผ preventDefault ๋“ฑ์„ ํฌํ•จํ•˜์—ฌ

๋ธŒ๋ผ์šฐ์ € ๊ณ ์œ  ์ด๋ฒคํŠธ์™€ ๋™์ผํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ฐ€์ง€์ง€๋งŒ ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•จ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

 

Synthetic Event ํƒ€์ž…

ํ•ฉ์„ฑ ์ด๋ฒคํŠธ๋Š” ๋ธŒ๋ผ์šฐ์ €์˜ ๊ณ ์œ  ์ด๋ฒคํŠธ (nativeEvent) ์™€๋Š” ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

๋ชจ๋“  ํ•ฉ์„ฑ ์ด๋ฒคํŠธ๋Š” BaseSyntheticEvent ๋ฅผ ์ƒ์†๋ฐ›์œผ๋ฉฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

 

interface BaseSyntheticEvent<E = object, C = any, T = any> {
    nativeEvent: E;
    currentTarget: C;
    target: T;
    bubbles: boolean;
    cancelable: boolean;
    defaultPrevented: boolean;
    eventPhase: number;
    isTrusted: boolean;
    preventDefault(): void;
    isDefaultPrevented(): boolean;
    stopPropagation(): void;
    isPropagationStopped(): boolean;
    persist(): void;
    timeStamp: number;
    type: string;
}

 

 

React ์˜ ์ด๋ฒคํŠธ๋“ค์€ ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ง•์„ ์œ„ํ•ด ๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ๊ฐ™์€ ์†์„ฑ์„ ๊ฐ€์ง€๋„๋ก ํ‘œ์ค€ํ™”ํ•ฉ๋‹ˆ๋‹ค.

React ์—์„œ ์ง€์›ํ•˜๋Š” ์ด๋ฒคํŠธ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋˜์–ด์žˆ์œผ๋ฉฐ ๋ชจ๋‘ EventHandler ํƒ€์ž…์œผ๋กœ๋ถ€ํ„ฐ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

 

type ReactEventHandler<T = Element> = EventHandler<SyntheticEvent<T>>;

type ClipboardEventHandler<T = Element> = EventHandler<ClipboardEvent<T>>;
type CompositionEventHandler<T = Element> = EventHandler<CompositionEvent<T>>;
type DragEventHandler<T = Element> = EventHandler<DragEvent<T>>;
type FocusEventHandler<T = Element> = EventHandler<FocusEvent<T>>;
type FormEventHandler<T = Element> = EventHandler<FormEvent<T>>;
type ChangeEventHandler<T = Element> = EventHandler<ChangeEvent<T>>;
type KeyboardEventHandler<T = Element> = EventHandler<KeyboardEvent<T>>;
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;

// ...

 

๊ทธ๋ฆฌ๊ณ  EventHandler ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

 

type EventHandler<E extends SyntheticEvent<any>> = { bivarianceHack(event: E): void }["bivarianceHack"];

 

์—ฌ๊ธฐ์„œ SyntheticEvent ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

 

interface SyntheticEvent<T = Element, E = Event> extends BaseSyntheticEvent<E, EventTarget & T, EventTarget> {}

 

์ด๋ฒคํŠธ ํƒ€์ดํ•‘์€ ์–ด๋–ป๊ฒŒ ํ•˜๋Š”๊ฒŒ ์ข‹์„๊นŒ?

React ๋Š” ์‹ค์ œ DOM API ๋“ค์— ๋Œ€ํ•œ ๋ž˜ํ•‘ ๊ฐ์ฒด๋ฅผ ์ œ๊ณตํ•˜๊ณ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— DOM ์ด๋ฒคํŠธ ํƒ€์ž…์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ• ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋Œ€์‹  ๋ชจ๋“  ์ด๋ฒคํŠธ๋“ค์ด SyntheticEvent ๋ฅผ ์ƒ์†๋ฐ›๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—

๋‹ค์Œ๊ณผ ๊ฐ™์ด name ์ด๋‚˜ value ๋“ฑ์˜ ์†์„ฑ์— ์ ‘๊ทผํ•  ๋•Œ ํƒ€์ž… ๋‹จ์–ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

 

export default function ButtonExample() {

  const handleClick = (e: SyntheticEvent) => {
    const target = e.target as HTMLButtonElement // โœ… type assertion
    console.log(target.value)
  }

  return <button onClick={handleClick}>๋ฒ„ํŠผ</button>
}

 

์ด๋Š” SyntheticEvent ์˜ target ์ด EventTarget ํƒ€์ž…์ด๋ฉฐ EventTarget ์€

๋‹ค์Œ๊ณผ ๊ฐ™์ด name ์ด๋‚˜ value ์™€ ๊ฐ™์€ ํŠน์ • ์—˜๋ฆฌ๋จผํŠธ์—์„œ ์ œ๊ณตํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ด๋Š” event.target ์ด ํ•ญ์ƒ DOM Element ์ž„์„ ๋ณด์žฅํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ ‡๊ฒŒ ๊ตฌ์กฐํ™” ๋˜์–ด์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

interface EventTarget {
    addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
    dispatchEvent(evt: Event): boolean;
    removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}

 

๊ฐœ์ธ ์ทจํ–ฅ ์ฐจ์ด์ด์ง€๋งŒ React ์—์„œ๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์— ๋Œ€ํ•œ ํƒ€์ž…๋„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์œผ๋‹ˆ

๋‹ค์Œ๊ณผ ๊ฐ™์ด ํƒ€์ž…์„ ์ •์˜ํ•  ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => { /** */ }

 

๐Ÿ“Œ React ๋‚ด๋ถ€์˜ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ

 

React Blog : React v17.0

 

React 17 ๋ถ€ํ„ฐ ๋ชจ๋“  ์ด๋ฒคํŠธ๋Š” document ๊ฐ€ ์•„๋‹Œ root DOM container ์— ํ• ๋‹น๋ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ root DOM ์€ ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๊ฐ€ ๋งˆ์šดํŠธ๋˜๋Š” ์ง€์ ์ž…๋‹ˆ๋‹ค.

๋•Œ๋ฌธ์— ์—˜๋ฆฌ๋จผํŠธ์— ํ• ๋‹นํ•œ ๋ชจ๋“  ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” root DOM Container ์— ํ• ๋‹น๋ฉ๋‹ˆ๋‹ค.

 

const rootNode = document.getElementById('root');
ReactDOM.render(<App />, rootNode);

 

๐Ÿ’ก ์™œ ์ด๋Ÿฌํ•œ ์„ ํƒ์„ ํ•œ ๊ฒƒ์ผ๊นŒ?

๋ฆฌ์•กํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํŠน์ • DOM ์š”์†Œ์— ์ด๋ฒคํŠธ๋ฅผ ํ• ๋‹นํ•˜๋”๋ผ๋„ ์„ฑ๋Šฅ์ƒ์˜ ์ด์œ ๋กœ

ํ•ด๋‹น ๋…ธ๋“œ์— ์ง์ ‘ ์ด๋ฒคํŠธ๋ฅผ ํ• ๋‹นํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

 

customButton.addEventListener('click', handleClick)

 

React 17 ์ด์ „์—๋Š” document ๋…ธ๋“œ์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ• ๋‹นํ•˜๋Š” event delegation ํŒจํ„ด์„ ํ™œ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ,

์ด๋Ÿฌํ•œ ๊ตฌ์กฐ๋กœ ์ธํ•ด ๋ช‡๊ฐ€์ง€ ๊ฒฝ์šฐ์— ๋Œ€ํ•ด์„œ ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿค” ๋ฌธ์ œ ์ƒํ™ฉ

๋งŒ์•ฝ ํ•˜๋‚˜์˜ ์•ฑ์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฆฌ์•กํŠธ ์•ฑ ์ธ์Šคํ„ด์Šค๋ฅผ ์ค‘์ฒฉํ•˜์—ฌ ์‚ฌ์šฉํ•  ๋•Œ,

stopPropagation ์˜ ๋™์ž‘์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์€ ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด ํ•œ ๊ฐœ๋ฐœ์ž๋Š” ๊ฐ๊ฐ์˜ ๋ฆฌ์•กํŠธ ์•ฑ์„ iframe ์œผ๋กœ ๊ฐ์‹ธ๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ,

์ด๋Š” ๊ฒฐ๊ตญ iframe ์ด ์ƒ์„ฑ ๋ฐ ํŒŒ๊ดด๋˜๋Š” ๊ณผ์ •์—์„œ ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์— ์˜ํ•ด ์„ฑ๋Šฅ์— ์ข‹์ง€์•Š์€ ์˜ํ–ฅ์„ ๋ฏธ์ณค๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

 

https://github.com/facebook/react/pull/8117

 

Attach event listeners at the root of the tree instead of document by vjeux · Pull Request #8117 · facebook/react

Context: we are investigating using React in order to build both Atom core components and Atom plugins. Work have been done last year to make sure that multiple React instances in the same app and ...

github.com

https://github.com/facebook/react/issues/7128

 

Clean up top-level event listeners after unmounting all roots · Issue #7128 · facebook/react

Do you want to request a feature or report a bug? Bug - maybe intended behaviour. What is the current behavior? Background I have an app that needs to be embedded by other apps (other customers). T...

github.com

 

์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด react ์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋“ค์„ document ๊ฐ€ ์•„๋‹Œ

ํ•ด๋‹น ๋ฆฌ์•กํŠธ ํŠธ๋ฆฌ์˜ ๋ฃจํŠธ๋…ธ๋“œ์— ํ• ๋‹นํ•˜๋„๋ก React v17 ๋ถ€ํ„ฐ ์ˆ˜์ • ๋˜์—ˆ๊ณ ,

์ด๋ฅผ ํ†ตํ•ด non-react(ex. jQuery) + react ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ์—๋„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ฆฌ์•กํŠธ๊ฐ€ ์˜๋„๋Œ€๋กœ ๋™์ž‘ํ•จ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ“Œ Synthetic Event ์™€ Native Event ์˜ ๋งคํ•‘ ๊ณผ์ •

๋ฆฌ์•กํŠธ๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” Synthetic Event ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—

๊ฒฐ๊ตญ์—๋Š” ์ด๋ฅผ Native Event ์™€ ๋งคํ•‘ํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ง€๊ธˆ๋ถ€ํ„ฐ๋Š” ์‹ค์ œ react ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด์„œ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋ง ๊ณผ์ •์ด ๋ถ€์ฐฉ๋˜๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

โš ๏ธ ์ดํ•ด๋„๋ฅผ ๋†’์ด๊ธฐ ์œ„ํ•ด ํ๋ฆ„ํŒŒ์•…์— ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๋Š” ๊ฐ„์†Œํ™”ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

์šฐ์„  ๋ฆฌ์•กํŠธ ๋ฃจํŠธ ๋…ธ๋“œ๋ถ€ํ„ฐ ์‚ดํŽด๋ด…์‹œ๋‹ค.

React v18 ๋ถ€ํ„ฐ๋Š” react-dom ํŒจํ‚ค์ง€์˜ createRoot ๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋ฆฌ์•กํŠธ ๋ฃจํŠธ๋…ธ๋“œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด createRoot ํ•จ์ˆ˜๋Š” ๋‚ด๋ถ€์˜ ReactDomRoot.js ์˜ createRoot ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

// @package/react-dom/src/client/ReactDom.js
import { createRoot as createRootImpl } from './ReactDOMRoot'

function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
  // ์ดˆ๊ธฐ ์กฐ๊ฑด ์ฒดํฌํ•˜๊ณ  ...

  return createRootImpl(container, options);
}
// @package/react-dom/ReactDomRoot.js

export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
  // ์˜ต์…˜๊ฐ’ ์„ค์ •, ์ดˆ๊ธฐ ์กฐ๊ฑด ์ฒดํฌ ๋“ฑ๋“ฑ ...

  const root = createContainer(
    // ...
  );
  markContainerAsRoot(root.current, container);

  if (enableFloat) {
    Dispatcher.current = ReactDOMClientDispatcher;
  }
  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container;

  listenToAllSupportedEvents(rootContainerElement); // โœ…

  return new ReactDOMRoot(root);
}

 

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์€ ReactDOMRoot ๊ฐ€ ๋ฐ˜ํ™˜๋˜๊ธฐ ์ด์ „์—

listenToAllSupportedEvents ๋กœ Native Event ๋ฅผ ๋ฃจํŠธ ๋…ธ๋“œ์— ์—ฐ๊ฒฐํ•˜๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด์ œ listenToAllSupportedEvents ๊ฐ€ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„๋˜์–ด์žˆ๋Š”์ง€ ์‚ดํŽด๋ณผ๊ฒŒ์š”.

 

// @package/react-dom-bindings/src/events/DOMPluginEventSystem

export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
  if (!(rootContainerElement: any)[listeningMarker]) {
    (rootContainerElement: any)[listeningMarker] = true;

    allNativeEvents.forEach(domEventName => {
      if (domEventName !== 'selectionchange') {
        if (!nonDelegatedEvents.has(domEventName)) {
          listenToNativeEvent(domEventName, false, rootContainerElement);
        }
        listenToNativeEvent(domEventName, true, rootContainerElement);
      }
    });

        // ...
  }
}

 

์—ฌ๊ธฐ์„œ๋Š” ๋ชจ๋“  Native Event ๋“ค์— ๋Œ€ํ•ด์„œ rootContainerElement ์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ถ€์ฐฉํ•˜๋Š” ๊ณผ์ •์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ nonDelegatedEvents ๋Š” DOM ์—์„œ ์ผ๊ด€๋˜๊ฒŒ ๋ฒ„๋ธ”๋ง๋˜์ง€ ์•Š๋Š” ์ด๋ฒคํŠธ๋“ค์ด๊ธฐ ๋•Œ๋ฌธ์—

ํ•ด๋‹น root container ์— ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋ถ€์ฐฉํ•˜์ง€ ์•Š๊ณ  ์‹ค์ œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ target element ์— ๋ถ€์ฐฉํ•ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์š”๋Ÿฐ ์ด๋ฒคํŠธ๋“ค์€ capture phase ์— ํ•ด๋‹นํ•˜๋Š” ๋ฆฌ์Šค๋„ˆ๋งŒ,

๊ทธ ๋ฐ˜๋Œ€์˜ ๊ฒฝ์šฐ์—๋Š” bubble phase + capture phase ์˜ ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋ถ€์ฐฉํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿ“Œ Synthetic Event ํ•ธ๋“ค๋ง ๊ณผ์ •

synthetic event ์™€ native event ๊ฐ„์˜ ๋งคํ•‘ ๊ณผ์ •์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด์•˜์œผ๋‹ˆ,

์ด์ œ ์‹ค์ œ๋กœ ๋ธŒ๋ผ์šฐ์ € ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ react ๋‚ด๋ถ€์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์•ž์„œ native event ๋ฅผ synthetic event ์™€ ๋งคํ•‘ํ•  ๋•Œ listenToNativeEvent ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•˜๋Š”๋ฐ,

์—ฌ๊ธฐ์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ addTrappedEventListener ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

// @package/react-dom-bindings/src/events/DOMPluginEventSystem.js

function addTrappedEventListener(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  isCapturePhaseListener: boolean,
  isDeferredListenerForLegacyFBSupport?: boolean,
) {
  let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
  );

  // ...

  if (isCapturePhaseListener) {
    if (isPassiveListener !== undefined) {
      unsubscribeListener = addEventCaptureListenerWithPassiveFlag(/** */);
    } else {
      unsubscribeListener = addEventCaptureListener(/** */);
    }
  } else {
    if (isPassiveListener !== undefined) {
      unsubscribeListener = addEventBubbleListenerWithPassiveFlag(/** */);
    } else {
      unsubscribeListener = addEventBubbleListener(/** */);
    }
  }
}

 

์—ฌ๊ธฐ์„œ createEventListenerWrapperWithPriority ๋ฅผ ํ†ตํ•ด listener ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ํ• ๋‹นํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

// @package/react-dom-bindings/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;
      break;
  }
  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer,
  );
}

 

์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ์ด๋ฒคํŠธ์˜ ์šฐ์„ ์ˆœ์œ„์— ๋”ฐ๋ผ listenerWrapper ์— ์•Œ๋งž์€ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ํ• ๋‹น๋˜๋Š” ํ•ธ๋“ค๋Ÿฌ๋Š” ๋ชจ๋‘ ์ตœ์ข…์ ์œผ๋กœ dispatchEvent ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉฐ

์ตœ์ข…์ ์œผ๋กœ dispatchEventsForPlugins ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

// @package/react-dom-bindings/src/events/DOMPluginEventSystem.js

function dispatchEventsForPlugins(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue: DispatchQueue = [];
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

 

 

getEventTarget ์—์„œ native browser ์—์„œ ์ผ๊ด€๋œ ๋ฐฉ์‹์œผ๋กœ ์ด๋ฒคํŠธ ๋Œ€์ƒ ์š”์†Œ๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋น„์–ด์žˆ๋Š” dispatchQueue ๋ฅผ ์ƒ์„ฑํ•œ ๋’ค, ์ด๋ฅผ ์ด์šฉํ•ด SimpleEventPlugin ์˜ extractEvents ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

// @package/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js

function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: DOMEventName,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: null | EventTarget,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
): void {
  const reactName = topLevelEventsToReactNames.get(domEventName);
  if (reactName === undefined) {
    return;
  }
  let SyntheticEventCtor = SyntheticEvent;
  let reactEventType: string = domEventName;
  switch (domEventName) {
    case 'keypress':
      if (getEventCharCode(((nativeEvent: any): KeyboardEvent)) === 0) {
        return;
      }
    case 'keydown':
    case 'keyup':
      SyntheticEventCtor = SyntheticKeyboardEvent;
      break;
    case 'focusin':
      reactEventType = 'focus';
      SyntheticEventCtor = SyntheticFocusEvent;
      break;
    case 'focusout':
      reactEventType = 'blur';
      SyntheticEventCtor = SyntheticFocusEvent;
      break;
    // ...
    default:
      break;
  }

    // ...

  const listeners = accumulateSinglePhaseListeners(/** */);
  if (listeners.length > 0) {
    const event: ReactSyntheticEvent = new SyntheticEventCtor(/** */);
    dispatchQueue.push({event, listeners});
  }
}

 

extractEvents ์—์„œ๋Š” native event ์— ํ•ด๋‹นํ•˜๋Š” synthetic event ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ

์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์™€ ํ•จ๊ป˜ ํ์— ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.

์ด๋•Œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋Š” accumulateSinglePhaseListeners ์—์„œ ์ฐพ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ํ•จ์ˆ˜์—์„œ๋Š” ์ด๋ฒคํŠธ ํƒ€์ผ“์˜ ํ˜ธ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ ๋ฐ ์Šค์ฝ”ํ”„์— ํ•ด๋‹นํ•˜๋Š” ๋ชจ๋“  ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋ฐฐ์—ด์„ ๋งŒ๋“ค์–ด์„œ ๋ฐ˜ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ๋‹ค์‹œ dispatchEventsForPlugins ๋กœ ๋Œ์•„๊ฐ€,

processDispatchQueue ์—์„œ ํ์— ํ• ๋‹น๋œ ํ•ธ๋“ค๋Ÿฌ๋“ค์„ ์ˆœ์„œ์— ๋งž๊ฒŒ ํ˜ธ์ถœํ•ด์ค๋‹ˆ๋‹ค.

 

๐Ÿ“Œ Event Pooling (โš ๏ธ removed in React v17)

React 17 ์ด์ „์—๋Š” SyntheticEvent ์‚ฌ์šฉ์— ๋”ฐ๋ฅธ ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด

Event Pooling ์ด๋ผ๋Š” ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋Š” ๋ˆˆ์— ๋„๋Š” ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ๊ณ , ์—ฌ๋Ÿฌ ๋ฆฌ์•กํŠธ ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ํ˜ผ๋ž€์„ ์ฃผ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

์ด๋Š” ๋ฆฌ์•กํŠธ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ € ์ด๋ฒคํŠธ์— ๋Œ€ํ•ด์„œ ์ด๋ฒคํŠธ ๊ฐ์ฒด ๋ฅผ ์žฌํ™œ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

๋ฆฌ์•กํŠธ๋Š” ์‹ค์ œ native ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋ž˜ํ•‘๋œ synthetic event ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—

์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ด์•ผํ•˜๊ณ , ํ•„์š” ์—†์–ด์ง„ ๊ฒฝ์šฐ์—๋Š” GC ์— ์˜ํ•ด ์‚ญ์ œ๊ฐ€ ํ•„์š”ํ•ด์ง‘๋‹ˆ๋‹ค.

์ด ๊ฒฝ์šฐ ์ด๋ฒคํŠธ๊ฐ€ ์ˆ˜์—†์ด ๋งŽ์•„์ง„๋‹ค๋ฉด ๋ฉ”๋ชจ๋ฆฌ์— ๋ถ€๋‹ด์„ ์ค„ ์ˆ˜ ์žˆ์œผ๋ฉฐ,

GC ๋˜ํ•œ ๋งŽ์€ ์ˆ˜ํ–‰์ด ํ•„์š”ํ•ด์ ธ CPU ์‹œ๊ฐ„์„ ๋งŽ์ด ์†Œ๋ชจํ•˜๊ธฐ ๋•Œ๋ฌธ์—

์ด๋ฒคํŠธ ์ธ์Šคํ„ด์Šค๋ฅผ ์žฌํ™œ์šฉํ•˜๋Š” synthetic instance pool ์„ ์œ ์ง€ํ•˜์—ฌ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

Event Pool ์˜ ๋™์ž‘ ๋ฐฉ์‹

 

์ด๋Ÿฌํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ํ†ตํ•ด GC ์˜ ์‹คํ–‰ ํšŸ์ˆ˜๋ฅผ ์ตœ์†Œํ™”ํ•˜๊ณ 

๋”์šฑ์ด ๊ฐ๊ฐ์˜ ์ด๋ฒคํŠธ๋งˆ๋‹ค ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ƒ์˜ ์ด์ ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋Ÿฌํ•œ ์„ค๊ณ„๋Š” ๋น„๋™๊ธฐ ์ฝœ๋ฐฑ ์„ ๋‹ค๋ฃจ๋Š” ๊ฒฝ์šฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋‹ค์Œ ์ฝ”๋“œ๋Š” React v16 ์—์„œ ์–ด๋–ค ๋ฌธ์ œ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์„๊นŒ์š”?

 

export default function Input() {
    const [data, setData] = useState('')

    const handleChange = (e) => {
      this.setData(() => ({
        name: event.target.value
      }));
    }
}

 

๋ฌธ์ œ๋Š” setState ์— updater ํ•จ์ˆ˜๋ฅผ ๋„˜๊ฒจ์ฃผ๊ณ , ์—ฌ๊ธฐ์„œ event.target ์— ์ ‘๊ทผํ•˜์—ฌ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

์ด๋Š” ๋ฆฌ์•กํŠธ๊ฐ€ ํŠน์ • ์ด๋ฒคํŠธ์˜ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‹คํ–‰์‹œํ‚จ ๋’ค ๋‹ค์‹œ pool ์— ๋„ฃ๊ธฐ ์œ„ํ•ด

event ์˜ ๋ชจ๋“  ์†์„ฑ๋“ค์„ null ๋กœ ์ดˆ๊ธฐํ™” ์‹œํ‚ค๊ธฐ ๋•Œ๋ฌธ์— ์ฐธ์กฐ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด event.persist ๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ ์ฐธ์กฐ๋ฅผ ์œ ์ง€ํ•œ๋‹ค๊ณ  ์•Œ๋ ค์ฃผ๊ฑฐ๋‚˜

value ๋ฅผ ๋ฏธ๋ฆฌ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๋ณ€์ˆ˜๋ฅผ ํ• ๋‹นํ•˜์—ฌ ์ฐธ์กฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. (ํด๋กœ์ €)

 

// React v17 ์ด์ „์—๋Š”..

handleChange(event) {
  event.persist(); // โœ… persist() ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ฑฐ๋‚˜,
  this.setData(() => ({ name: event.target.value }));
}

handleChange(event) {
  const name = event.target.value // โœ… ํ˜น์€ ํด๋กœ์ €๋ฅผ ํ™œ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
  this.setData(() => ({ name }));
}

 

๐Ÿ“Œ ์ฐธ๊ณ ์ž๋ฃŒ

์ด๋ฒคํŠธ ์ฒ˜๋ฆฌํ•˜๊ธฐ - React

React v17.0 Release Candidate: No New Features - React Blog

How to perform event handling in React [Tutorial] | Packt Hub

Web: React์˜ Event ์‹œ์Šคํ…œ ๋‚ด๋ถ€ ๊ตฌํ˜„ ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ (React v18.2.0)

Why does the newer 'setState' method not recognize my event.target?

React Deep Dive- React Event System (1)

Web: React์˜ Event ์‹œ์Šคํ…œ ๋‚ด๋ถ€ ๊ตฌํ˜„ ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ (React v18.2.0)

๋ฐ˜์‘ํ˜•

๐Ÿ’ฌ ๋Œ“๊ธ€