본문 바로가기

IT

Effective Typescript - 33 - string 타입보다 더 구체적인 타입 사용하기

string 타입은 매우 넓은 범위를 가집니다. 따라서 string 타입으로 변수를 선언하려 한다면, 좀 더 구체적이고 좁은 타입이 더 적절하지 않은지 고민이 필요합니다.

// 비추
interface Album {
    artist: string;
    title: string;
    release: string; //YYYY-MM-DD
    recordingType: string; // 예를 들어 "live" "studio"
}

// 추천
type RecordingType = "studio" | "live" //  
interface AlbumFix {
    artist: string;
    title: string;
    release: Date;
    recordingType : RecordingType
}

위와 같이 추천 방식을 이용해 타입의 범위를 좁히면, 의도하지 않은 값이 설정되는 것을 미리 방지하는데 도움이 된다.

저자는 string 타입을 최대한 좁혀서 설계하면 3가지 장점이 있다고 소개합니다.

 

첫 번째, 타입을 명시적으로 정의함으로써 다른 곳으로 값이 전달되어도 타입 정보가 그대로 유지합니다.

//함수 호출시 파라메터 타입이 단순히 string이 아니라 구체적 허용되는 값이 명시되어 있음.
function getAlbumsOfType(recordingType: RecordingType): AlbumFix[] {
    //...
}

두 번째, 타입을 명시적으로 정의하고 해당 타입의 의미를 주석으로 설명하기에 유리하다.

/** 녹음환경이 어떤 곳인지 구별하는 타입 */
type RecordingType = "studio" | "live" //

세 번째, keyof 연산자로 더욱 세밀하게 객체의 속성 체크가 가능해진다.

//underscore.js 함수의 pluck이라는 함수가 있다고함. 해당 함수는 아래와 같이 동작함.
var group: AlbumFix[] = [
        { artist: 'Dreatm Threater', recordingType: "studio", release: new Date('1999-12-12'), title: "Dance of the eternity"},
        { artist: 'Queen', recordingType: "studio", release: new Date('1990-10-12'), title: "Let me live"}
    ];
//pluck(group, 'artist'); //결과 ['Dreatm Threater', 'Queen']


// 여기서, pluck 함수의 시그니처를 정의하려면 어떤 방식이 이상적일까?

//비추천) key의 값이 string으로 T에서 허용하지 않는 key를 허용함.
// 반환 타입 any는 너무 넓기 때문에 반환 타입 좁히기 원칙에 어울리지 않는 설계임.
function pluck<T>(record: T[], key: string): any[]

//추천하는 방식
function pluck<T, K extends keyof T>(records: T[], key: K): T[K][]{
    return records.map(r => r[key]);
}

 

[요약]

* 문자열을 남발하여 선언된 타입 설계를 기피해야한다. 되도록이면 최대한 구체적으로 타입을 설계하는 것이 좋다.

* 변수의 범위를 보다 정확하게 표현하고싶으면 string 타입보다는 허용 가능한 문자열 조합(유니온)을 타입으로 설계하는 것이 좋다.

* 객체 속성의 이름을 함수 매개변수로 받을 때는 string 보다는 keyof T를 사용하는 것이 좋다.