TypeScript 타입 관련 메모 모음
구조적 타입 시스템
TypeScript는 이름이 아니라 구조로 타입 호환성을 판단한다. 한 타입이 다른 타입의 속성을 모두 가지고 있으면 그 타입으로 쓸 수 있다.
type Time = { hours: number; minutes: number };
const full = { hours: 1, minutes: 30, seconds: 15 };
const time: Time = full; // OK - Time의 속성을 모두 가지고 있음
단, 객체 리터럴을 직접 대입할 때는 초과 속성 검사가 적용되어 에러가 난다.
const time: Time = { hours: 1, minutes: 30, seconds: 15 }; // ❌ 'seconds'는 Time에 없음
필요한 속성만 타입으로 받기
구조적 타이핑 덕분에, 콜백 파라미터에는 실제로 쓰는 속성만 적어도 된다.
const adminIds = coaches.map((coach: { adminId: number }) => coach.adminId);
coach에 다른 속성이 많아도 adminId만 있으면 된다. 다만 coaches의 요소 타입이 이미 정의되어 있다면 타입을 생략하고 추론에 맡기는 편이 낫다.
제네릭
기본값으로 타입 확장하기
export type CustomError<T = unknown> = AxiosError<CustomData<T>>;
CustomError는 응답 데이터 형태가 CustomData<T>인 AxiosError다. T를 생략하면 기본값 unknown이 쓰인다. 이렇게 하면 error.response?.data.message처럼 서버 에러 응답 구조에 맞게 접근할 수 있다.
제네릭을 명시하지 않으면?
호출할 때 타입 인자를 생략하면 TypeScript는 인자로부터 추론한다.
const identity = <T,>(value: T) => value;
identity('a'); // T = string (추론)
identity<string>('a'); // 명시
추론할 단서가 없으면 제약 조건(extends)이 있으면 그 타입, 없으면 unknown이 된다.
const create = <T,>(): T[] => [];
create(); // unknown[]
useState([])가 never[]로 추론되거나 axios.get(url)의 data가 any가 되는 것처럼, 추론할 근거가 부족한 곳에는 타입 인자를 명시한다.
axios 요청에 쿼리 스트링 넣기
const getHealthInfo = async ({ sectionId, ...params }: GetHealthInfoParams) => {
const response = await api.get<CustomData<GetHealthInfoRes>>(`/guide/${sectionId}`, {
params,
});
return response.data;
};
경로에 들어갈 값은 구조 분해로 빼고, 나머지는 params로 넘기면 axios가 ?key=value 쿼리 스트링으로 만들어 준다. undefined 값은 쿼리에서 제외된다.
Nullish 병합 연산자 ??
const size = input ?? 10;
왼쪽이 null 또는 undefined일 때만 오른쪽을 쓴다. 0, '', false는 유효한 값으로 남는다. ||는 이런 falsy 값도 모두 오른쪽으로 바꾸므로, 0이나 빈 문자열이 의미 있는 값이라면 ??를 쓴다.