타입 체크가 되긴 하지만 any 타입이 있어 정밀하지 못하다. 특히 Item 38에 나오겠지만 반환 값에 any를 쓰는 건 좋지 않은 설계이다.
타입 시그니처를 개선하기 위해 제너릭 타입을 도입하자.
function pluck<T>(records: T[], key: string): any[] {
return records.map(r => r[key])
// ~~~~~~ '{}' 형식에 인덱스 시그니처가 없으므로
// 요소에 암시적으로 'any' 형식이 있습니다.
}
이제 타입스크립트는 key의 타입이 string이라 범위가 너무 넓다는 오류가 발생한다. Album의 배열을 매개변수로 전달하면 기존의 string 타입의 넓은 범위와 반대로, key는 단 네 개의 값('artist', 'title', 'releaseDate', 'recordingType')만이 유효하다.
keyof의 설명을 돕기 위해 준비된 다음 예시는 keyOf Album 타입으로 얻는 예시이다.
type K = keyof Album // 'artist' | 'title' | 'releaseDate' | 'recordingType'이 된다
이제 string을 keyof T로 바꾸면 된다.
function pluck<T>(records: T[], key: keyof T) { // T[keyof T][]로 추론된다.(객체 내 가능한 모든 타입)
return records.map(r => r[key]) // 마우스를 올려보면 추론된 타입을 볼 수 있다.
}
const releaseDate = pluck(album, 'releaseDate') // 타입이 (string | Date)[]
이 코드는 타입 체커를 통과하며, 타입스크립트가 반환 타입을 추론할 수 있다.
하지만 keyof T는 string에 비하면 범위가 좁긴 하지만 여전히 넓다.
범위를 좁히기 위해 keyof T의 부분 집합으로 제너릭 매개변수를 추가하자. 더 좁혀보자.
function pluck<T, K extends keyof T>(records: T[], key: K): T[K][] {
return records.map(r => r[key])
}
pluck(albums, 'releaseDate') // 타입이 Date[]
pluck(albums, 'artist') // 타입이 string[]
pluck(albums, 'recordingType') // 타입이 RecordingType[]
pluck(albums, 'recordingDate')
// ~~~~~~~~~~~~~ '"recordingDate"' 형식의 인수는
// ... 형식의 매개변수에 할당될 수 없습니다.
이제 타입 시그니처가 완벽해졌다. 언어 서비스는 Album의 키에 자동 완성 기능을 제공할 수 있게 되었다. (Item 42)
보다 정확한 타입을 사용하면 오류를 방지하고 코드의 가독성을 향상시킬 수 있다.
Summary
'stringly typed' 코드를 피하자. 모든 문자열을 할당할 수 있는 string 타입보다는 더 구체적인 타입을 사용하자.
변수의 범위를 보다 정확하게 표현하고 싶다면 string 타입보다는 문자열 리터럴 타입의 유니온을 사용하자.
타입 체크를 더 엄격히 하고 생산성을 향상시킬 수 있다.
객체의 속성 이름을 함수 매개변수로 받을 때는 string이 아닌 keyof T를 사용하는 것이 좋다.