복잡한 세상.. 편하게 개발하게 해주는 타입 🤔
저는 주로 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
를 사용하면 선언된 객체를 바탕으로 프로퍼티의 타입을 몽땅 가져와 사용할 수 있어요. 아래와 같은 객체를 선언했다고 해봅시다.
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
를 사용하면 선언된 객체 형태의 타입의 프로퍼티 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
는 함수 타입 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
은 함수 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);
이번 글에서는, 제가 최근에 Parameters
와 ReturnType
을 직접 맛(?)보았던 이야기들과 느낀 점들에 대해 정리해보았어요. 이제는 내가 선언한 것 뿐만 아니라, 라이브러리에서 제공되는 타입들도 요리조리 잘 써먹어봐야겠다는 생각이 들었던 거 같다고 소감(?)을 말해보며... 글을 마무리해보겠습니다 :)