TYPESCRIPT 문법 정리 2편

김승재·2022년 11월 25일
0

Vue

목록 보기
3/3

이 글은 TYPESCRIPT 문법 정리 1편에서 이어지는 Typescript 문법 정리 글입니다.


5. Generics

Generic을 사용하면 함수나 클래스의 선언 시점이 아닌, 사용 시점에서 타입을 선언할 수 있습니다. 즉, 타입을 좀 더 유동적으로 받아서 처리할 수 있게 되죠.


// 기존 선언 시점에서의 타입 지정
function identity(arg: number): number {
  return arg;
}

// Generic을 활용한 사용시점에서의 타입 선언
function identity<Type>(arg: Type): Type {
  return arg;
}

console.log(identity<number>(1)); // returns 1 
console.log(identity<string>('1')); //returns '2'

위와 같이 기존의 방식보다 Generic을 이용하면, 여러 타입이 들어와도 처리할 수 있는 로직을 구현 가능합니다.
만약, Type 에 String 과 Number만 받고 싶다면 아래와 같이 "extend" 구문을 이용하여 제약을 추가할 수 도 있습니다.

function identity<Type extends string | number>(arg: Type): Type {
  return arg; // only string and number can be placed for the type
}

하지만, Generic 을 사용할 경우, 각각의 고유의 타입이 가지고 있는 bulit-in 유틸리티 함수/변수 (예를 들어 Array.length)를 사용할 때 주의 해야됩니다. 아래의 예제를 한번 보면,

function loggingIdentity<Type>(arg: Type): Type {
  console.log(arg.length);
//Property 'length' does not exist on type 'Type'.
  return arg;
}

위와 같이 저희는 arg 라는 변수에 length라는 필드가 있는것을 확신할 수 없기 때문에, 다음과 같은 에러가 발생합니다. 그렇기 때문에, 위에서 설명한 "extends"를 활용해서 해당 변수에 다음과같이 제약을 부여하여, 에러를 피해갈 수 있습니다.

interface Lengthwise {
  length: number;
}
 
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
  console.log(arg.length); // Now we know it has a .length property, so no more error
  return arg;
}

이 이외에도 조건부 타입 (Contional Type) 와 같이 삼항연산자 (e.g. T extends U ? X : Y)을 이용하여 타입을 구현 할수도 있지만, 이것까지 활용하진 않을거같기 때문에 저는 패스했습니다. 관심 있는 분들은 Contional Types을 확인해보시면 좋을거같습니다.


6. Type Declarations

Type declaration은 변수, 상수, 함수, 또는 클래스가 프로젝트 어딘가에 이미 선언되어 있는 것을 알리는 것입니다. 그리하여, JS 코드로는 컴파일 되지 않고, Typescript 컴파일러에게 타입 정보를 알리기만 한다고 하네요. 예를 들어, JS 코드로 미리 프로젝트에 존재하는 유틸리티 (대표적인 예로 String-utils)에 타입 선언을 위해 사용이 가능하겠네요.

A. 이미 존재하는 String 유틸리티 파일

이해가 안되실수 있을거 같아서 한 가지 예제를 설명하면, 아래의 js 모듈은 string utils는 이미 다른 프로젝트에서 가져온 것이며, 해당 js 모듈을 typescript 프로젝트에서 사용하기 위해서는 타입을 선언(declare)해야됩니다.

// node_modules/str-utils/index.js

function strReverse(value) {
    return value.split('').reverse().join('');
}

function strToLower(value) {
    return value.toLowerCase();
}

function strToUpper(value) {
    return value.toUpperCase();
}

function strRandomize(value) {
    var array = value.split('');
    for (var i = array.length - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    return array.join('');
}

function strInvertCase(value) {
    return value
        .split('')
        .map(function(c) {
            if (c === c.toLowerCase()) {
                return c.toUpperCase();
            } else {
                return c.toLowerCase();
            }
        })
        .join('');
}

module.exports = {
    strReverse,
    strToLower,
    strToUpper,
    strRandomize,
    strInvertCase
};

B. Type Declare 코드 추가

여기에서 파일의 확장자를 보시면 Declare의 d가 추가된 [파일이름].d.ts 으로 되신것을 확인할 수 있습니다. 해당 파일은 다른 불필요한 로직없이 오직 타입 선언만을 위해서 존재하는 파일을 의미합니다.

// str-utils/index.d.ts
declare module 'str-utils' {
    type StrUtil = (input: string) => string;

    export const strReverse: StrUtil;
    export const strToLower: StrUtil;
    export const strToUpper: StrUtil;
    export const strRandomize: StrUtil;
}

C. 해당 str-utils를 다른 ts 파일에서 모듈로써 사용가능하게 됨

// 타입스크립트로 프로젝트의 어느 파일
import {
    strReverse,
    strToLower,
    strToUpper,
    strRandomize,
    strInvertCase
} from 'str-utils';

만약 str-utils/index.d.ts이 존재하지 않는다면, 해당 모듈이 import 되는 시점에 모듈의 매개변수/반환변수 타입이 unknown으로 설정되어, 모듈을 이용하는 부분이 굉장히 까다로워 지게됩니다.


7. Module Augmentation

Module Augmentation은 이미 type이 선언된 모듈에 추가적인 타입을 업데이트할 수 있는 타입스크립트의 기능 중 하나입니다. JS에서는 기본적으로 Merging을 지원하지 않지만, Typescript는 Merged Types들이 있기 때문에, 이미 선언된 타입에 추가적인 필드를 추가할 수 있습니다.

A. 타입이 이미 선언된 datewizard 유틸리티 모듈

이번에도 이해가 난해할 수 있기때문에, 한가지 예를 들어볼려고 합니다.
아래의 코드는 datewizard라는 Date관련 유틸리티 모듈입니다. 하지만, 해당 Datewizard의 DateDetails 인터페이스에는 오직 년/월/일 필드만 있고, 시간관련 필드는 없는 상황입니다.

//node_modules/date-wizard/index.d.ts

declare function dateWizard(date: Date, format: string): string;

declare namespace dateWizard {
    interface DateDetails {
        year: number;
        month: number;
        date: number;
    }

    function dateDetails(date: Date): DateDetails;
    function utcDateDetails(date: Date): DateDetails;
}

export = dateWizard;

B. Module Augmentation을 이용해서 시간관련 필드를 추가 가능

다음과 같이 모듈을 호출하여, DateDetails 인터페이스를 한번 더 명시하여 (Merged Types를 활용하여), 시간관련 필드를 추가할 수 있습니다.

// module-augmentations/date-wizard/index.d.ts
// This enables module augmentation mode.
import 'date-wizard';

declare module 'date-wizard' {
    const pad: (ident: number) => string;

    interface DateDetails {
        hours: number;
        minutes: number;
        seconds: number;
    }
}

8. Type mapping

원래는 Type mapping 까지 정리할려고 하였지만, 이 부분까지는 아직 제 실력이 부족하여, 좀 더 기초적인 문법에 익숙해진 뒤에 정리하는게 좋을거같아서 뒤로 미룰려고 합니다 ㅠㅅㅠ
이 주제에 대해서 궁금하신 분들은 Mapped-types를 확인하시면 좋을거 같습니다.

profile
웹 개발자,김승재입니다 !

0개의 댓글