JavaScript/React

React, TS에서의 Event Type interface와 React Event System 구조

남희정 2023. 11. 29. 02:01

Introduction

 

내가 겪은 비슷한 사례의 Type Error (원제 : TypeScript: How to access onChange event data without error? (Object is possibly 'null'))

 

나는 null이 아니라 any 형식이 포함된다고 Error가 떴다. React에서의 event를 제대로 이해하지 못한 탓인지 tsx를 화나게 했다. 나처럼 js에서의 Event는 익숙한데, React + TypeScript에서의 Event 타입을 어떻게 다루어야 할 지 이해하지 못하는 분들을 위해 글을 쓴다!

React, TypeScript에서의 Event Type

JavaScript에서 자주 쓴 `addEventListener( )`, `on____`

 

예시로 `onChange`

change event는 주로 input element의 값이 수정되었을 때 발생하도록쓰인다. 

 

// Syntax
addEventListener("change", (event) => {});

onchange = (event) => {};

// Example
const result = document.querySelector(".result");

selectElement.addEventListener("change", (event) => {
	result.textContent = `You like ${event.target.value}`;
});

 

TSX에서 쓰려하면..?

const App = () => {
  const [minutes, setMinutes] = useState(0);
  const [flipped, setFlipped] = useState(false);
  const onChange = (event) => {
    setMinutes(event.target.value);
  };
  
  // typescript 오류 
  // 'event' 매개 변수에는 암시적으로 'any' 형식이 포함됩니다.
  
  return
  
  ... 생략

 

➡️ 이런식으로 당연히 event를 알겠거니 하고 event.target.value를 썼다간 오류를 뱉어낸다. 그렇다고 위험한 any를 쓸 순 없고,,, 어떻게 타입을 명시해주어야 할까? 

 

해결

(event: React.ChangeEvent<HTMLInputElement>)

  const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setMinutes(Number(event.target.value));
  };

 

React에서 Input element와 onChange 이벤트를 사용하려면 `React.ChangeEvent<HTMLInputElement>`로 타입을 설정해주어야 한다.

✔️ input 엘리먼트는 TypeScript에서 <HTMLInputElement>타입을 사용하고, textarea 엘리먼트의 경우 <HTMLTextAreaElement>타입을 사용한다. 

 

React에서의 엘리먼트와 이벤트 타입에 대해서 공부하다보니 이런 인터페이스들이 전부 리액트 자체적으로 제공하는 안정성 있는 이벤트 핸들링 시스템인 React Event System의 일부였기에 트리 구조를 따라 구조를 알아보고 싶었다. 

 

property Type을 말해주는 친절한 VScode

 

👀 onChange에 커서를 올리면 IDE가 타입을 알고 말해준다. 

 

Event의 타입! 이벤트 Interface 살펴보기

1. ChangeEvent Interface

// ChangeEvent는 SyntheticEvent를 상속받는다.

interface ChangeEvent<T = Element> extends SyntheticEvent<T> {
	target: EventTarget & T;
}

 

✔️ ChangeEvent 인터페이스는 React에서 사용되는 이벤트 핸들러에 전달되는 합성 이벤트 객체의 구조를 정의한 인터페이스 중 하나로, 주로 DOM에서 발생한 변경 이벤트의 구조를 나타낸다.

✔️ SyntheticEvent 인터페이스를 상속하여 공통적인 속성과 메서드를 제공한다.

✔️ 주요 특징? 변경 이벤트를 발생시킨 요소에 대한 참조를 가지고 있다는 것.

➡️ target이라는 프로퍼티로 표현, 변경 이벤트를 Trigger한 DOM요소를 가리킨다. 이를 통해 입력값의 변경을 쉽게 감지하고 처리할 수 있다.

 

(1) <T = Element > ? 인터페이스 Element? 끝은 어디일까?

interface Element extends Node, ARIAMixin, Animatable, ChildNode, InnerHTML, NonDocumentTypeChildNode, ParentNode, Slottable {
	...
}

// Element가 정의된 부분을 보자.

interface Element {}
interface DocumentFragment {}

interface HTMLElement extends Element {}
interface HTMLAnchorElement extends HTMLElement {}
interface HTMLAreaElement extends HTMLElement {}
interface HTMLAudioElement extends HTMLElement {}
interface HTMLBaseElement extends HTMLElement {}
interface HTMLBodyElement extends HTMLElement {}
interface HTMLBRElement extends HTMLElement {}
interface HTMLButtonElement extends HTMLElement {}

...
// 상속 과정
EventTarget
    |
    +-- Node
        |
        +-- Element
                |
                +-- HTMLElement

상속 Tree

Element는 문서의 모든 Element 인터페이스가 참조하는 가장 일반적인 인터페이스이다. 모든 종류의 엘리먼트 인터페이스에 공통적인 메서드와 프로퍼티만 가지고 있다. <T = Element>부터 각각의 상속 인터페이스로 거슬러 올라갔다. (HTMLElement, HTMLInputElement 등은 Element 인터페이스를 확장하는 구체적인 인터페이스)

 

(2) target : EventTaget?

그림에 있는 EventTarget 인터페이스는 웹 API의 일부로, 이벤트를 수신할 수 있는 모든 객체의 기본 인터페이스이다. EventTarget은 메서드(addEventListener)와 속성을 정의한다. Element(HTMLElement 등), Document(Node) 및 Window와 같은 DOM 객체들은 EventTarget 인터페이스를 구현한다. 즉, 이들의 객체가 Event Listener를 추가하거나 제거하는 EventTarget의 메서드를 사용할 수 있다는 것..!

 

TypeScript의 Element 인터페이스는 타입 체킹과 객체의 구조를 정의하고, JavaScript의 EventTarget 인터페이스는 이벤트 리스닝 기능을 제공하는 것이다. 

 

2. SyntheticEvent Interface 

// SyntheticEvent는 BaseSyntheticEvent를 상속받는다.

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

 

SyntheticEvent 인터페이스는 리액트의 이벤트 처리 시스템에서 중요한 역할을 하는 인터페이스이다. 이 인터페이스는 리액트에서 제공하는 모든 이벤트 핸들러에 전달되는 *합성 이벤트 객체의 타입을 정의한다.

(위에서 ChangeEvent가 상속받았던 인터페이스)

 

*합성 이벤트의 목적? 

React에서는 모든 플랫폼에서 동일한 이벤트 처리, 일관성을 제공하기 위해 설계되었다. 브라우저마다 다른 방식으로 작동하는 웹의 원시 이벤트native Event를 Wrapping한다. 추상화하여 일관된 이벤트 처리를 할 수 있는 인터페이스를 제공하는 것이다. 

 

SyntheticEvent와 Native Event의 차이점?

위에서 말했듯 SyntheticEvent는 브라우저의 원시 이벤트를 감싸는 Wrapper이다. native Event의 표준화 및 추상화를 목적으로 한다. 리액트의 이벤트 처리 시스템에 특화된 인터페이스이며 리액트의 컴포넌트 트리 내에서 이벤트를 효율적으로 처리한다.

(이벤트 핸들링 동작에 관해선 따로 글을 써볼까한다.)

SyntheticEvent
    |
    +-- ChangeEvent
    |
    +-- FocusEvent
    |
    +-- KeyboardEvent
    |
    +-- MouseEvent
    |
    +-- TouchEvent
    |
    +-- WheelEvent

 

3. BaseSyntheticEvent Interface

React Event System의 핵심 인터페이스리액트에서 발생하는 모든 이벤트의 기본적인 구조와 기능을 정의한다.

// BaseSyntheticEvent
	// Event System
	// ----------------------------------------------------------------------
	// TODO: change any to unknown when moving to TS v3
            interface BaseSyntheticEvent<E = object, C = any, T = any> {
            // 원래의 브라우저 이벤트. React의 합성 이벤트는 브라우저의 원시 이벤트를 감싸고 있으며, nativeEvent를 통해 원래 이벤트에 접근할 수 있다.
            nativeEvent: E;
            // 이벤트가 발생한 DOM 요소(currentTarget, target)
            // 이벤트 핸들러가 부착된 요소
            currentTarget: C;
            // 이벤트가 실제로 발생한 요소
            target: T;
            // 이벤트의 생명주기를 관리하는 속성 5개
            // 이벤트가 어떻게 전파되고 처리되는지를 나타냄
            bubbles: boolean;
            cancelable: boolean;
            defaultPrevented: boolean;
            eventPhase: number;
            isTrusted: boolean;
            // 이벤트 흐름 제어 메서드 4개
            // 이벤트의 기본 동작을 방지하거나 이벤트 전파를 제어하는 데 사용
            preventDefault(): void;
            isDefaultPrevented(): boolean;
            stopPropagation(): void;
            isPropagationStopped(): boolean;
            // React17에서 이벤트 객체가 풀링(pooling)되어 재사용됐다. persist() 메서드를 호출하면 이벤트 객체를 풀에서 제거하여 지속적으로 사용할 수 있다.
            persist(): void;
            // 이벤트가 발생한 시간과 이벤트의 타입을 나타냄
            timeStamp: number;
            type: string;
    }

 

기본 속성과 메서드에 대한 설명을 주석으로 달았다.

 

SyntheticEvent와의 관계

모든 SyntheticEvent 객체는 BaseSyntheticEvent를 상속받는다. SyntheticEvent가 BaseSyntheticEvent의 모든 속성과 메서드를 가지고 있다는 것을 의미한다. SyntheticEvent는 이 기본 인터페이스를 확장하여 특정 유형의 이벤트(예: 클릭, 입력 변경 등)에 대한 추가적인 속성과 메서드를 제공한다.

 

🤔 💭

어제부터 React 기초로 영화 웹사이트 만들기 중이다. 과거의 좋은 강의는 최신 버전으로 스스로 마이그레이션 한다. 스펙을 맞추라고 생각할 수 있으나,,,해당 강의는 jsx로 진행하고 React17버전을 따른다. TypeScript를 잘 쓰고 싶고 바꾸기 전에 React17의 레거시 코드도 확인하고 바꾸는 과정에서 둘 다 익혀지니까 일석이조 아닌가 싶다. 그리고 마이그레이션 꽤 재밌다.

다만 나는 tsx와 React를 제대로 실무에 적용해보지 않은 푸른 떡잎이라는 것~..

이전에 React Router라던가, 몇 개를 포스팅하고 싶었는데 망설여졌다. React에 대한 전문가의 좋은 강의와 책, 콘텐츠는 이미 레드오션이라 생각한다. ChatGPT, Bard 같은 AI에게도 충분히 도움을 받을 수 있기에 내가 배운 기초를 써서 무엇하나 했다.

블로그란 나 스스로 반추하기 위함도 있지만 자고로 다른 사람들에게도 좋은 영향을 줄 수 있어야된다고 생각해서 스스로 떳떳하지 못하면 쓸 수가 없더라. 그럼에도 불구하고 오늘은 쓰려는 이유는 무엇인가?

10년 후까지는 모르겠다만, 근 몇 년간 내가 사용해야될 기술 스택이기 때문이고, 잘하고 싶다는 욕심때문이다. 그리고 단순히 코드 비교하고 "이렇게 쓰면 됩니다" 라는 식의 포스팅을 지양하면 되지 않을까? 떡잎이기 때문에 앞으로도 내가 배우는 건 흔히, 자주 쓰이는 개념일 것이고 깊게 다루고 싶을 것이니.. 일반적인 docs와 다른 글을 쓸 수 있어야겠다.

 

React Event Type System...인터페이스를 상속하며 타입이 좁혀지는 걸 확인할 수 있었다. lib.d.ts, index.d.ts 파일을 보며 어떻게 설정했는지도 뜯어볼 수 있었다. 브라우저 native event와 흡사하지만 같은 것은 아니라는 것도 꽤나 흥미로웠던 것 같다. 이벤트에 관해서는 특히 잘 적용할 수 있으리라....! tsx가 더이상 화를 내지 않길 바라며 앞으로도 잘 살펴볼 예정이다.

 


[Responding to Events]

[HTMLElement: change event]

[Type the onChange event of an element in React (TypeScript)]

[3. 타입스크립트로 리액트 상태 관리하기]

[리액트 + 타입스크립트 (React + TypeScript): HTML 요소, 폼의 이벤트 처리]

[handleChange in TypeScript]

[React 이벤트 처리방식과 SyntheticEvent]

[EventTarget]

[DOM 조작 (DOM Manipulation)]

[이벤트 처리하기]

[Responding to Events]

[객체지향 개념 정리와 중요한 원칙]

ChatGPT, Bard 🌞