타입스크립트 실전 - Prop, eventHandler

유키미아우·2023년 12월 2일
0

타입스크립트의 기본기인 이넘, 유니언, 제네릭, 인터페이스 등등을 일단 머릿속에 꾸역꾸역 넣어보았다. 이제 난 타입스크립트를 쓸 줄 알게 된것인가? 슬프게도 그렇지 않다..
React + JS 프로젝트를 TS 프로젝트로 마이그레이션할시 interface는 무슨 일을 하며 generic은 어디에 끼워넣으면 좋단말인가?

공부하면서 알게 된 Typescript 실전 사용법을 기록해본다.

Prop과 eventHandler

Prop은 Typescript를 접하기 전 이미 Proptypes 검사를 해본적 있다면 금방 익숙해질 수 있을 것 같다.
eventHandler의 경우 예시 prop으로 내려받은 함수를 통해 타입지정법을 알아보자.

Button.jsx 컴포넌트 예시

import PropTypes from "prop-types";

function Button({ className, children, disabled, onClick }) {
  return (
    <button
      className={className}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

Button.propTypes = {
  className: PropTypes.string,
  children: PropTypes.node.isRequired,
  disabled: PropTypes.bool,
  onClick: PropTypes.func.isRequired,
};

네개의 prop을 받고 있는 Button 컴포넌트이다.
type은 문자열,
disabled는 true or false의 불리언,
children은 문자열부터 이미지, 컴포넌트 등까지 이르는 폭 넓은 값,
className은 문자열,
onClick은 함수가 들어올 것이라고 설정해두었다.

타입스크립트에서는 위와 같은 타입체크를 interface로 할 수 있다. 형태만 조금 바뀌었을 뿐 매우 비슷하다.

Button.tsx 컴포넌트 예시

interface ButtonProps {
  className?: string;
  children: ReactNode;
  disabled?: boolean;
  onClick: (e: MouseEvent<HTMLButtonElement>) => void;
}

function Button({ className, children, disabled, onClick }: ButtonProps) {
  return (
    <button
      className={className}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

☝️ ReactNode, MouseEvent는 반드시 최상단에서 “react”에서 import해줄 것

옵셔널 체이닝

눈여겨볼 특징으로는 옵셔널 체이닝을 이용하여 isRequired 체크를 간단히 해줄 수 있다는 점이다.

  • className?: string;
    className이 truthy하다면 string 타입일 예정이야.
  • className: string;
    className은 string 타입일 예정이야.

MouseEvent

onClick: (e: MouseEvent<HTMLButtonElement>) => void;

MouseEvent는 Typescript에 내장된 이벤트타입이다.
내부를 들여다보면 최상위 계층에 SyntheticEvent가 있고 그 자손으로 UIEvent가 있으며 그 아래 또 다시 여러 종류의 이벤트들이 존재한다.

SyntheticEvent (조상님)
🔻extends
UIEvent (아버지)
🔻extends
MouseEvent (자식)

따라서 (e: SyntheticEvent) 설정으로 모든 이벤트를 퉁치는 것도 가능하나, 타입체크는 엄밀할수록, 세밀할수록 존재의의가 빛날것이다. 따라서 MouseEvent만이 전달될 것이 100% 확실하다면 자식뻘인 MouseEvent를 설정해주도록 하자.

예시인 Button컴포넌트와는 별개의 이야기지만 만약 Input컴포넌트가 Prop으로 onChange 핸들러를 받는 상황이라면 아래처럼 응용해볼 수 있겠다.

onChange: (e: ChangeEvent<HTMLInputElement>) => void;

제네릭

제네릭은 코드의 재사용성을 높이기 위한 기능이다.

interface Test<T, U> {
  first: T;
  second: U;
}

// 사용 예
let test1: Test<number, string> = { first: 1, second: "two" };
let test2: Test<string, boolean> = { first: "hello", second: true };

Test<number, string>는 타겟(first와 second)에 대한 타입을 동적으로 정의하고 있는 모습이다.

아래 예시에서도 마찬가지로 이벤트의 타겟(Dom 노드)에 대한 타입이 작성되어있다.

MouseEvent<HTMLButtonElement>

Button 컴포넌트이므로 당연히 button태그인 HTMLButtonElement가 해당되며, 이 밖에도 100개 이상 존재한다.

물론 인텔리센스가 자동완성을 도와주므로 위를 전부 외울 필요는 없다. 상황에 따라 골라서 설정해주면 좋다.

void

버튼 컴포넌트에 전달되는 eventHandler 함수는 값을 반환하기보다는 다른 함수를 내부에서 연쇄적으로 실행시킬 가능성이 매우 높다. 그러므로 return값이 없음을 => void;라고 표현해주고 있다.

그러나 만약 Prop interface 내부에 작성하는 함수가 어떤 값을 반환하는 경우에는 void 대신에 반환하고자 하는 값의 타입을 지정할 수 있다.

onClick: (e: MouseEvent<HTMLButtonElement>) => string;

🔺 함수의 return값이 "typescript 공부" 일 경우 예시

다른 베리에이션

onClick: (e: MouseEvent<HTMLButtonElement>) => void;
onClick: MouseEventHandler<HTMLButtonElement>

취향껏 사용할 수 있겠으나 통일감에 유의하면 좋다.

profile
능동적인 마음

0개의 댓글