복세편개 타입 잘 쓰고 싶은 사람.. 나 🙋‍♀️

Doeunnkimm·2024년 2월 7일
4

TypeScript

목록 보기
3/3
post-thumbnail
post-custom-banner

내가 선정한.. 복세편개 타입

복잡한 세상.. 편하게 개발하게 해주는 타입 🤔

  • typeof
  • keyof
  • Parameters
  • ReturnType

저는 주로 TypeScript를 사용하고 있는데요! 쓰다보면 유기적으로 잘 엮어 쓰고 있을 때 더욱 견고하게 쓰고 있다는 느낌을 받는 거 같아요.

유기적으로 잘 엮어 쓰고 있다의 간단한 예시를 들어볼게요! (단순한 설명을 위해 유틸 타입은 사용하지 않았어요!)

// good
interface ModalProps {
  title: string
  content: string
}
interface ModalBodyProps {
  content: ModalProps['content']
}

// bad
interface ModalProps {
  title: string
  content: string
}
interface ModalBodyProps {
  content: string
}

위와 같이 간단하더라도, 비록 타입이 string이더라도, 연관이 있다면 엮어주어서 유지보수할 때 편해야 한다고 생각합니다.

즉, 기존에 있는 타입들을 요리조리 잘 써먹을 수 있어야 한다는 말과도 같아요. 그래서 제가 이번 글에서 소개할 타입들은 기존에 있는 것들을 이용할 수 있게 해주는 타입들이에요.

typeof

typeof를 사용하면 선언된 객체를 바탕으로 프로퍼티의 타입을 몽땅 가져와 사용할 수 있어요. 아래와 같은 객체를 선언했다고 해봅시다.

const man = {
  name: 'bytefer',
  email: 'bytefer@gmail.com',
  address: {
    street: 'Kulas Light',
    suite: 'Apt. 556',
    city: 'Gwenborough',
    zipcode: '92998-3874',
    geo: {
      lat: '-37.3159',
      lng: '81.1496'
    }
  }
}

그럼 이 데이터 형태와 동일한 객체를 안전하게 또 선언하고 싶으면 위 프로퍼티를 하나하나 보면서 타입을 만들어줄 수도 있겠지만, typeof를 사용하면 자동으로 타입을 정의할 수 있어요.

// bad
type Person = {
  name: string;
  email: string;
  address: {
    street: string;
    suite: string;
    zipcode: string;
    geo: {
      lat: string;
      lng: string
    }
  }
}

// good
type Person = typeof man

단순한 객체일 때는 큰 문제가 되지 않겠지만, 객체가 크고 복잡한 경우에는 keyof를 통해 훨씬 쉽게 타입을 정의할 수 있습니다.

keyof

keyof를 사용하면 선언된 객체 형태의 타입의 프로퍼티 key를 유니온 타입으로 구성할 수 있는데요! 즉, 선택지로서 프로퍼티 키를 사용하고 인자로 이 프로퍼티키를 받아 사용해야 할 경우 유용합니다.

아래와 같은 선택지로 사용할 객체가 있다고 해봅시다.

type Person = {
  name: string;
  age: number;
  married: boolean;
}

위와 같이 선언된 객체 형태의 타입에서 프로퍼티 키를 값으로 사용하고 싶다면 keyof가 유용해요.

// bad
type PersonKeys = 'name' | 'age' | 'married'

// good
type PersonKeys = keyof Person

keyof는 보통 typeof랑 더 많이 사용되는 거 같은데요! 선언되어 있는 객체가 있고, 그 객체의 프로퍼티 키를 통해 값을 사용해야 하는 상황에 매우 유용합니다!

const sizeCSS = {
  small: 'h-[10px]',
  medium: 'h-[15px]',
  large: 'h-[20px]',
}

type SizeProps = keyof typeof sizeCSS // 'small' | 'medium' | 'large'

function Foo({size}: {size: SizeProps}) {
  return (
    <Bar size={sizeCSS[size]} />
  )
}

위와 같이 작성함으로써 sizeCSS에 선언한 프로퍼티 키 만을 타입으로 하여 받을 수 있으므로 반드시 매칭이 될 수 있도록 할 수 있습니다.

참고로, keyof typeof의 과정은 아래와 같습니다.

type Typeof = typeof sizeCSS // { small: string; medium: string; large: string }

type KeyofTypeof = keyof Typeof // 'small' | 'medium' | 'large'

만약에 keyof typeof를 사용하지 않고 sizeCSS를 보며 타입을 선언해주었다면,

const sizeProps = 'small' | 'medium' | 'large'

sizeCSS에 프로퍼티가 추가 혹은 수정되었을 때 일일이 변경점을 찾아 수정해줘야 할 것입니다. 번거롭다 번거로워..

반면, keyof typeof를 통해 타입을 정의해주면, 객체가 변경되어도 자동으로 인식이 되기 때문에 타입은 따로 신경써주지 않아도 됩니다.

Parameters

Parameters는 함수 타입 Type의 매개변수에 사용된 타입들의 튜플 타입을 생성합니다.

type Type = Parameters<(a: string, b: number) => void> // [string, number]

Parameters를 사용하면 메서드의 매개변수의 타입을 튜플로 받아 사용할 수 있습니다. 좀 더 와닿을 수 있는 예시로, 기존에 선언한 메서드를 이용한다면 아래와 같습니다.

function testMethod(a: string, b: number) {
  // ...
}
type Type = Parameters<typeof testMethod> // [string, number]

🫢 실제로 유용하다고 느꼈을 때

최근에 api 요청에 성공했을 때, 응답 객체의 타입에 대해 조금 커스텀 해줘야 하는 task가 있었어요. 저희 팀에서는 httpClient 라이브러리로 axios를 사용하고 있어서 axios의 http 메서드를 사용했을 때 return 값을 다뤄야 했습니다.

즉, axios.get에 인자를 넘기고 사용하는 건 다 동일한데, return값만 손을 봐야했던 거죠?

axios.get에 마우스를 갖다대면 아래와 같이 타입을 알려줬어요.

그래서 처음에는 아래와 같은 형태의 타입으로 정의되었었어요(return되는 데이터에서 result라는 프로퍼티에 한번더 감싸져 있는 형태라 BaseResponse라는 유틸 타입을 선언하고 이를 감싸주었어요).

const api = {
  get: <T, R>(url: string, params?: R): Promise<AxiosResponse<BaseResponse<T>>> => createAxiosInstance.get(url, { params }),
  ...
}

단순히 기존 메서드의 매개변수를 사용하고 싶다의 의미였는데 명시되어 있는 타입을 따라가다보니 위와 같이 되었던 거죠. 그래서 위 타입을 Parameters를 이용해 개선하면 아래와 같은 형태가 될 수 있습니다.

const api = {
    get: <T>(...args: Parameters<typeof axiosInstance.get>) => {
    return axiosInstance.get<BaseResponse<T>>(...args);
  },
  ...
}

확실히 가독성도 좋아지고 한결 깔끔해질 수 있었습니다. 🥲

이런 식으로, 라이브러리의 메서드에서 인자는 그대로 사용하면서도 커스텀이 필요할 때 Parameters를 사용하면 매우 유용할 것 같다는 생각이 들었습니다.

ReturnType

ReturnType은 함수 Type의 반환 타입으로 구성된 타입을 생성합니다.

type Type = ReturnType<() => string>  // string

좀 더 일반적이게 기존 메서드를 이용해보면 아래와 같습니다.

function testMethod(name: string, age: string) {
  return `안녕하세요. 저는 ${age}${name}입니다.`
}
type Type = ReturnType<typeof testMethod> // string

🫢 실제로 유용하다고 느꼈을 때

최근에 디바운스 작업을 해야했었어요. useDebounceCall이라는 커스텀 훅을 만들어 작업을 했습니다. 내부 로직은, useRef로 선언된 timer에 값을 넣어놓고, 이 값을 통해 clearTimeout하기도 하고, 다시 여기에 새로운 setTimeout을 넣기도 해야 했어요.

setTimeout에 마우스를 갖다대니 아래와 같이 타입을 알려주었습니다.

그래서 리턴 타입인 NodeJS.Timeout을 보고 아래와 같이 타입을 명시했어요.

const timer = useRef<NodeJS.Timeout | null>(null);

사실 위 예시에서 봤던 axios.get 보다는 간단하고 가독성에 해가 되는 정도까지는 아니였지만, 좀 더 setTimeout과 유기적으로 사용하고 싶다면 아래와 같이 ReturnType을 활용할 수 있습니다.

  const timer = useRef<ReturnType<typeof setTimeout> | null>(null);

이번 글에서는, 제가 최근에 ParametersReturnType을 직접 맛(?)보았던 이야기들과 느낀 점들에 대해 정리해보았어요. 이제는 내가 선언한 것 뿐만 아니라, 라이브러리에서 제공되는 타입들도 요리조리 잘 써먹어봐야겠다는 생각이 들었던 거 같다고 소감(?)을 말해보며... 글을 마무리해보겠습니다 :)

profile
개발자와 사용자 모두의 눈👀을 즐겁게 하는 개발자가 되고 싶어요 :) 👩🏻‍💻
post-custom-banner

0개의 댓글