본문 바로가기

IT

Effective Typescript - 14 - 타입 연산과 제네릭 사용으로 반복 줄이기.

같은 코드를 반복하지 않는 것도 중요하지만, 타입스크립트의 타입에 관한 코드도 예외가 될 수 없다.

아래의 코드는 타입스크립트 타입의 정의에서 발생하는 반복과 그것을 해결하는 타입 연산에 관한 예시이다.

 

1. 타입 연산을 활용한 타입 관련 코드 중복 제거 기법

a. 인터페이스의 확장으로 중복 줄이기

//name key, age key가 반복됨.
interface Person{
    name: string;
    age: number;
}
interface PersonSpan {
    name: string;
    age: number;
    job: string;
}

//인터페이스의 확장으로 중복 줄이기
interface PersonBase {
    name: string;
    age : number;
}

interface PersonWithJob extends PersonBase {
    job: string
}

interface PersonWithBirth extends PersonBase {
    birth: Date
}

b. 중복된 형태의 타입을 타입 엘리어스나 인터페이스를 이용해서 중복 줄이기

//{stock: string, amount: number }가 반복됨
function exchange(a: {ticker: string, amount: number}, b: {ticker: string, amount: number }): {ticker: string, amount: number} {
    return {
        ticker : a.ticker + '/' + b.ticker,
        amount : a.amount + b.amount
    }
}

// 타입 엘리어스 선언
type StockExchageInfo = {
    ticker: string,
    amount: number
}

function exchangeFix(a: StockExchageInfo, b: StockExchageInfo): StockExchageInfo {
    return {
        ticker : a.ticker + '/' + b.ticker,
        amount : a.amount + b.amount
    }
}

c. 중복된 형태의 함수 타입 시그니처가 공유 되고 있을 때, 함수 시그니처를 타입 엘리어스로 선언하여 중복 줄이기.

//중복된 함수 시그니처로 함수 statement 구현
function max(a: number, b: number): number {
    return Math.max(a, b);
}

function min(a: number, b: number): number {
    return Math.min(a, b);
}

// 함수 시그니처를 타입으로 정의 후 함수 expression으로 함수 구현
type CompareNum = (a:number, b:number) => number
const max_fix:CompareNum = (a, b) => (Math.max(a, b));
const min_fix:CompareNum = (a, b) => (Math.min(a, b));

d. 인터페이스 속성타입 정의시 인덱싱을 사용하여 중복 제거

// 인덱싱을 통한 속성타입의 중복 제거
interface AppStatus {
    sesson_token: string;
    recent_activity: Date,
    contents: string
    login_statue : boolean;
}

interface SubAppStatus {
    sesson_token: string;
    recent_activity: Date,
    contents: string
}

interface SubAppStatusFix {
    sesson_token: AppStatus['sesson_token'];
    recent_activity: AppStatus['recent_activity'];
    contents: AppStatus['contents'];
}

//https://www.typescriptlang.org/docs/handbook/utility-types.html (유틸리티 타입의 Pick 항목을 참조)
type SubAppStatusFix2 = {
    [k in 'sesson_token' | 'recent_activity' | 'contents']: AppStatus[k]
};
type SubAppStatusFix3 = Pick<AppStatus, 'sesson_token'| 'recent_activity'| 'contents'>;

e. 테그된 유니온에서의 중복 제거

interface LoginRequest {
    type: 'login'
}

interface LogoutRequest {
    type: 'logout'
}

type Requests = LoginRequest | LogoutRequest;
type RequestTypes = 'login' | 'logout' // 이것은 중복에 해당한다.
type RequestTypesFix = Requests['type']; // 중복의 해결 방안: <결과> 'login' | 'logout'

c. 속성 이름은 동일한 두 개의 인터페이스(필수 속성, 옵션 속성) 에서의 중복 제거 예시

interface Properties {
    uuid: string,
    date: Date,
    auth: boolean
}

interface PropertiesUpdate {
    uuid?: string,
    date?: Date,
    auth?: boolean
}

type PropertiesUpdateFix = {[k in keyof Properties]?: Properties[k]}; // 중복의 제거

d. 값으로 선언된 변수로 부터 타입을 정의 하는 예제

const DEFAULT_PROP = {
    a: 1,
    b: 2,
    c: 'a',
    d: 'b'
};

interface Props {
    a: number,
    b: number,
    c: string,
    d: string,
};
//자바스크립트 runtime typeof 연산자가 아님

type PropsFix = typeof DEFAULT_PROP;

2. 제네릭을 활용한 타입 관련 코드의 중복 제거

제네릭은 타입을 위한 함수로 인식하면 이해하는데 도움이 된다.

함수에서 매개변수로 엉뚱한 인자가 넘어오는 것을 방지하기 위해 타입스크립트 타입 시스템이 이용되는 것 처럼, 타입을 위한 함수인 제네릭도 이와 같은 메커니즘이 적용될 필요가 있다. 이 메커니즘을 적용하기 위해 extends 키워드를 활용할 수 있다.

 

a. 제네릭의 extends를 이용한 타입 좁히기

type Couple<T extends Name> = [T, T];

const couple: Couple<Name> = [
    {first: 'dig', last: 'da'},
    {first: 'ff', last: 'gg'}
]; // 정상 (Couple 타입의 제네릭이 Name 인터페이스를 extends 하는 형식이기 때문)

const couple2:Couple<{last: string}> = [
    {last: 'ss'},
    {last: 'bb'},
];// 제네릭으로 넘긴 인자 '{last:string}'은 'extends Name'에 만족하지 못하는 타입이므로 오류이다.