React에서 Event키 이벤트를 등록하는 과정에서 타입스크립트 에러를 만났다. 에러의 원인을 파악하고 올바른 타입을 사용하여 Enter키 이벤트를 등록하는 과정을 살펴보자.
KeyboardEventconst handleKeydownInput = (e: KeyboardEvent) => {
if (e.isComposing || e.keyCode === 229) return;
if (e.code !== 'Enter') return;
addListItem();
};
<input onKeyDown={handleKeydownInput} />;
// ~~~~~~~~~ ts Error
TS Error:
Type(e: KeyboardEvent) => voidis not assignable to typeKeyboardEventHandler<HTMLInputElement>.
Types of parameters 'e' and 'event' are incompatible.
TypeKeyboardEvent<HTMLInputElement>is missing the following properties from typeKeyboardEvent: isComposing, initKeyboardEvent, DOM_KEY_LOCATION_STANDARD, DOM_KEY_LOCATION_LEFT, and 14 more.ts(2322)
TypeScript에는 JavaScript의 KeyboardEvent 타입이 있다. 초기 구현 코드에서 handleKeydownInput 이벤트 핸들러의 이벤트 객체(e)에 선언한 타입은 JavaScript의 KeyboardEvent이다.
하지만, React는 브라우저 호환성을 위해 자바스크립트의 이벤트 객체(이하 native event)를 그대로 사용하지 않고 synthetic event로 native event를 감싸서 synthetic event를 제공한다.
때문에 input의 onKeydown는 native event인 KeyboardEvent가 아닌 React에서 제공하는 synthetic event를 제공해줘야 한다. KeyboardEvent에 해당하는 React의 synthetic event 타입은 KeyboardEventHandler<T>이다.
(리액트의 이벤트 시스템이 궁금하다면 콴다 팀블로그에서 작성한 React Deep Dive - React Event System(1) 시리즈를 읽어보자.)
KeyboardEvent<HTMLInputElement> 타입 제공const handleKeydownInput = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.isComposing || e.keyCode === 229) return;
// ~~~~~~~~~~~ ts Error
if (e.code !== 'Enter') return;
addListItem();
};
TS Error: Property 'isComposing' does not exist on type 'KeyboardEvent'.
이벤트 객체에 KeyboardEvent<HTMLInputElement> 타입을 지정해주었지만, 또 다른 에러가 발생했다. isComposing이 지정해준 타입의 이벤트 객체에 존재하지 않는다는 것이다. KeyboardEvent<T> 타입을 살펴보자.
// KeyboardEvent
interface KeyboardEvent<T = Element> extends UIEvent<T, NativeKeyboardEvent> {
altKey: boolean;
/** @deprecated */
charCode: number;
ctrlKey: boolean;
code: string;
/**
* See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
*/
getModifierState(key: ModifierKey): boolean;
/**
* See the [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#named-key-attribute-values). for possible values
*/
key: string;
/** @deprecated */
keyCode: number;
locale: string;
location: number;
metaKey: boolean;
repeat: boolean;
shiftKey: boolean;
/** @deprecated */
which: number;
}
React에서 제공하는 KeyboardEvent 이벤트를 살펴보면 isComposing property가 존재하지 않는다. KeyboardEvent가 확장 받은 타입들을 타고 가보자.
// UIEvent
interface UIEvent<T = Element, E = NativeUIEvent> extends SyntheticEvent<T, E> {
detail: number;
view: AbstractView;
}
KeyboardEvent가 확장하고 있는 UIEvent는 SyntheticEvent를 확장하고 있다. 리액트를 사용하고 있다면 SyntheticEvent가 굉장히 익숙할 것이다. 위에도 언급했듯 리액트는 native event가 아닌 synthetic event를 제공한다.
리액트 공식문서에서는 synthetic event에 대해 다음과 같이 말한다.
Your event handlers will receive a React event object. It is also sometimes known as a “synthetic event”.
It conforms to the same standard as the underlying DOM events, but fixes some browser inconsistencies.
Some React events do not map directly to the browser’s native events. For example in
onMouseLeave,e.nativeEventwill point to amouseoutevent. The specific mapping is not part of the public API and may change in the future. If you need the underlying browser event for some reason, read it frome.nativeEvent.
역시 친절한 React 공식문서는 잊지 않고 nativeEvent에 접근하는 방법도 알려준다. 이제 내가 원하는 isComposing에 참조하기 위해서는 e.nativeEvent의 isComosing에 접근해야 한다는 걸 알았다.
코드를 변경하기 전에, 그럼 nativeEvent는 정확히 어디에 있는건지 궁금하지 않은가? 먼저 nativeEvent property가 어디에 있는건지 살펴보자.
// SyntheticEvent
interface SyntheticEvent<T = Element, E = Event>
extends BaseSyntheticEvent<E, EventTarget & T, EventTarget> {}
// 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;
}
BaseSyntheticEvent가 nativeEvent property를 가지고 있다.
알아낸 방법으로 코드를 수정했다.
import { KeyboardEvent } from 'react';
const handleKeydownInput = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
if (e.code !== 'Enter') return;
addListItem();
};
SyntheticEvent로 감싼 새로운 이벤트 객체를 제공한다. 즉, React Element에서 발생한 이벤트는 native event가 아닌 SyntheticEvent를 이벤트 객체로 가진다.SyntheticEvent에는 native event의 속성을 모두 가지고 있진 않다. native event에 접근하기 위해서는 이벤트 객체의 nativeEvent 프로퍼티를 참조해야 한다.