interface Album {
artist: string;
title: string;
releaseDate: string; // YYYY-MM-DD
recordingType: string; // "live" | "studio"
}
const kindOfBlue: Album = {
artist: "artist",
title: "kind Of Blue",
releaseDate: "22-03-04",
recordingType: "LIVE"
}
타입에서는 타입체커가 오류를 확인 하지 못하지만, YYYY-MM-DD
식의 형태를 처리하는 함수가 짜여 있을때, 오류가 발생 하게 된다.
타입 체커가 정확히 체크 할 수 있도록 타입을 구체적으로 작성 해야한다.
type RecordingType = "live" | "studio"
interface Album {
artist: string;
title: string;
releaseDate: Date; // YYYY-MM-DD
recordingType: RecordingType; // "live" | "studio"
}
const kindOfBlue: Album = {
artist: "artist",
title: "kind Of Blue",
releaseDate: "22-03-04", // 에러 발생 (Type 'string' is not assignable to type 'Date'.(2322))
recordingType: "LIVE" // 에러 발생 (Type '"LIVE"' is not assignable to type '"live" | "studio"'. Did you mean '"live"'?(2820))
}
keyof
연산자와 제네릭
을 통해서 매개변수의 타입을 좁히기
/** const extractObjectArrayByKey: (objs: any[], key: string) => any[] */
const extractObjectArrayByKey = (objs: any[], key: string) => {
return objs.map(obj => obj[key])
}
objs
가 any[]
이기 때문에, 객체의 배열이 아닌 다른 값의 배열이 들어오게 되면 오류가 발생하게 된다any[]
가 반환되면 어떤 타입인지 확인 할 수 없다const extractObjectArrayByKey: <T>(objs: T[], key: string) => any[] = (objs, key) => {
return objs.map(obj => obj[key])
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'unknown'. No index signature with a parameter of type 'string' was found on type 'unknown'.(7053)
}
제네릭
과 keyof 사용하기
// function extractObjectArrayByKey<T>(objs: T[], key: keyof T): T[keyof T][]
function extractObjectArrayByKey <T>(objs: T[], key: keyof T) {
return objs.map(obj => obj[key])
}
/**
function extractObjectArrayByKey<{
a: string;
b: number;
c: Date;
}>(objs: {
a: string;
b: number;
c: Date;
}[], key: "a" | "b" | "c"): (string | number | Date)[]
const answer: (string | number | Date)[]
*/
const answer = extractObjectArrayByKey([{ a: "1", b: 2, c: new Date('2030-12-29') }], 'a')
오류
이렇게 작성 할 경우, return 값인 answer가 문자열의 배열일지 숫자의 배열인지 타입이 좁혀주지 못한다.
예를들어) 문자에 대한 메소드인 answer.toUpperCase()
등에 접근을 못하게 된다
Key
타입을 제네릭화 하면, 제네릭에 따라 반환타입이 좁혀지는 결과를 얻을 수 있다
// function extractObjectArrayByKey<T>(objs: T[], key: keyof T): T[keyof T][]
function extractObjectArrayByKey <T, K extends keyof T>(objs: T[], key: K) {
return objs.map(obj => obj[key])
}
/**
function extractObjectArrayByKey<{
a: string;
b: number;
c: Date;
}, "a">(objs: {
a: string;
b: number;
c: Date;
}[], key: "a"): string[]
const answer: string[]
*/
const answer = extractObjectArrayByKey([{ a: "1", b: 2, c: new Date('2030-12-29') }], 'a')
const answer = extractObjectArrayByKey([{ a: "1", b: 2, c: new Date('2030-12-29') }], 'd') // 에러 발생
타입스크립트는 덕 타이핑
을 사용하기 때문에 아래와 같은 상황이 벌어진다.
type Vector2D = {
x: number;
y: number;
}
function calculoator(point: Vector2D, point2: Vector2D): Vector2D {
return { x: point.x + point2.x, y: point2.y + point2.y }
}
const pointer3D = { x:2, y: 3, z: 4 }
const pointer2D = { x:2, y: 3 }
calculoator(pointer2D, pointer3D) // 오류 발생 안함
실제로 객체의 속성이 다르더라도, 덕 타이핑
을 이용하기 때문에, Vector2D
의 속성이 객체에 모두 있으면, 타입체커를 통과 하게 된다.
사소한 실수로 이러한 것들을 막기 위해서는 브랜드 네이밍
을 붙이면 실수를 어느정도 방지 할 수 있다.
type Point = {
x: number;
y: number
}
type Vector2D = Point & { _brand: "2D" }
type Vector3D = Point & { z: number, _brand: '3D' }
function calculoator(point: Vector2D, point2: Vector2D): Vector2D {
return { x: point.x + point2.x, y: point2.y + point2.y, _brand: "2D" }
}
const pointer2D = { x:2, y: 3 }
const pointer3D = { x:2, y: 3, z: 4 }
function vector2D(x: number, y: number): Vector2D {
return { x, y, _brand: '2D' }
}
function vector3D(x: number, y: number, z: number): Vector3D {
return { x, y, z, _brand: '3D' }
}
calculoator(vector2D(pointer2D.x, pointer2D.y), pointer3D(pointer3D.x, pointer3D.y, pointer3D.z)) // This expression is not callable. Type '{ x: number; y: number; z: number; }' has no call signatures.(2349)