blog

TypeScript 타입 관련 메모 모음

Front-end / TypeScript#TypeScript#제네릭#구조적 타이핑#axios#팁 모음

구조적 타입 시스템

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이나 빈 문자열이 의미 있는 값이라면 ??를 쓴다.