리액트 이벤트
- 리액트는 가상 DOM을 다루면서 이벤트도 별도로 관리한다.
onclick
, onchange
같이 DOM 엘리먼트에 등록되는 이벤트 리스너와 달리, 리액트 컴포넌트(노드)에 등록되는 이벤트 리스너는 onClick
, onChange
처럼 카멜 케이스로 표기한다.
- 리액트 이벤트는 브라우저의 고유한 이벤트와 완전히 동일하게 동작하지 않는다. 리액트 이벤트 핸들러는 이벤트 버블링 단계에서 호출된다.
- 리액트는 브라우저 이벤트를 합성한 합성 이벤트(SyntheticEvent)를 제공한다.
type EventHandler<Event extends React.SyntheticEvent> = (
e: Event
) => void | null;
type ChangeEventHandler = EventHandler<ChangeEvent<HTMLSelectElement>>;
const eventHandler1: GlobalEventHandlers['onchange'] = (e) => {
// Event.target: EventTarget | null
console.log(e.target);
};
const eventHandler2: ChangeEventHandler = (e) => {
// React.ChangeEvent<HTMLSelectElement>.target: EventTarget & HTMLSelectElement
console.log(e.target);
};
- 리액트의 SyntheticEvent을 사용하면 이벤트 핸들러의 타입을 지정할 수 있다.
const Select = () => {
const handleChange: React.ChangeEventHandler<HTMLSelectElement> = (e) => {
// TODO
};
return <select onChange={handleChange}></select>;
};
제네릭 컴포넌트
- 함수 컴포넌트도 함수이므로 제네릭을 사용한 컴포넌트를 구현할 수 있다. 제네릭을 지정한 뒤 props를 넘겨주면 타입을 자동으로 추론해준다.
interface SelectProps<OptionType extends Record<string, string>> {
options: OptionType;
selectedOption?: keyof OptionType;
onChange?: (selected?: keyof OptionType) => void;
}
const Select = <OptionType extends Record<string, string>>({
options,
selectedOption,
onChange,
}: SelectProps<OptionType>) => {
return <></>;
};
const fruits = {
apple: '사과',
banana: '바나나',
blueberry: '블루베리',
};
const FruitSelect = () => {
// selectedOption 타입을 자동으로 추론
return <Select options={fruits} selectedOption="apple" />;
};
HTMLAttributes
className
, id
와 같은 리액트 컴포넌트의 기본 props를 추가할 때 직접 추가해줘도 되지만 리액트에서 제공하는 타입을 사용하면 더 정확한 타입을 설정할 수 있다.
- ComponentPropsWithoutRef는 리액트 컴포넌트에서 ref를 제외한 prop 타입을 반환해준다.
- Pick 유틸리티 타입을 통해 ReactProps에서 여러 개의 속성 타입을 가져올 수 있다. 이를 통해 인터페이스를 확장해준다.
type ReactSelectProps = React.ComponentPropsWithoutRef<'select'>;
interface SelectProps<OptionType extends Record<string, string>>
extends Pick<ReactSelectProps, 'id' | 'className'> {
options: OptionType;
selectedOption?: keyof OptionType;
onChange?: (selected?: keyof OptionType) => void;
}