ice rabbit programming

[JS][TS] Typescript의 Decorator와 Type Guard 본문

Development/JavaScript

[JS][TS] Typescript의 Decorator와 Type Guard

판교토끼 2020. 12. 31. 01:47

이전 글에서도 언급했듯이, Decorator와 Type Guard를 마지막으로 typescript 개념 정리는 1차적으로 마칠 예정이다. 추후에 다른 강의를 듣거나, 사용하다가 팁 등을 알게 되면 올릴 것 같다.

Decorator

Decorator는 Class, Method, Parameter, Property에 붙일 수 있는 특수한 선언이다. @를 붙여서 표현하는데, 생김새는 자바의 annotation과 흡사하다. @뒤에는 선언에 대한 정보와 함께 런타임에 호출되는 함수여야 한다. 역시 아래 코드를 보자.

class decorator

function hello(constructFuntion: Function) {
    // signature는 생성자를 인자로 지정하는 것
}

function helloFactory(show: boolean) {
    if (show) {
        return hello
    }
    return null
}

@helloFactory(true)
class Person {
    // ...
}

 

이러한 코드를 적으면, Person 클래스 객체를 만들지 않아도 Decorator가 실행된다. 즉, helloFactory가 실행되고 인자로 true가 넘어갔으므로 실제 동작인 hello가 불러진다.
만일 hello를 직접 부르면 인자 생성자는 class의 생성자가 넘어간다(ex. @hello(Person.constructor()). 하지만 직접 부르기보다는 위처럼 factory 형태로 부르는 경우가 많다.

위 구조에서, decorator에서 받은 생성자에 함수를 추가하면, 추가는 되지만 실제 class에서 멤버로 인식을 못한다. 그래서 호출하려면 빨간줄이 나오고 컴파일 에러가 발생하는데, (p).hello()와 같은 식으로 강제로 캐스팅을 통해서 호출할 수는 있다.

method decorator

함수에 적용하는 것도 클래스에서의 사용과 크게 다르지 않다.

function editable(canBeEdit: boolean) {
    // 아래 return이 signature이다.
    return function(target: any, propName: string, description: PropertyDesciprot) {

    }
}

class Person {
    constructor() {
        @editable(true)
        hello() { }
    }
}

const p = new Person()
p.hello() // hello가 호출
p.hello = function() {}
p.hello() // @ 호출에서 editable 파라미터가 true이면 17번째 줄에서 바꾼 hello가 불러진다.
// false이면 바뀌지 않고 원래 hello가 불러진다.

 

property decorator

함수에서의 사용과 비슷하게, function을 return해주면 된다. 아래처럼 활용 가능하다.

constructor(name: string, @print age: number) {

}

Type Guard

자바스크립트는 기본적으로 타입 추론을 가지고 있다고 포스팅에서 몇 번 언급했다. 타입을 명시적으로 쓰지 않았을 때 추론하는 방법에 대한 규칙이 있는데, let은 기본 자료형으로, const는 literal type으로.
그런데 array나 함수의 return에서는 타입을 원하는대로 얻기가 힘들다. 아래 예시를 보자.

// array example
const array1 = [] // empty
const array2 = ['a', 'b'] // string
const array3 = ['a', 1, false] // Union Type(string | number | boolean)

// function example
function exampe() {
    if(blah) {
        return true
    }
    else {
        return 0
    }
}

 

이럴 때 Type Guard를 활용하면 된다. Type Guard는 컴파일 타임에 type을 알 수 없을 때, 컴파일러에게 이를 알려주는 용도이다. 위처럼 두 가지 타입이 인자로 넘어오거나 return을 줄 때, 다음처럼 영역을 나누어서 해결할 수 있다.

interface Person {
    name: String
    age: number
}

// Type Guard
function isPerson(arg: any): arg is Person {
    return arg.name !== undefined
}

function hello(obj: Person | Car) {
    // 여기서는 어떤 타입인지 알 수 없으므로
    // Type Guard 호출
    if (isPerson(obj)) {
        // 이 분기 안은 Person의 공간이 된다.
        // 컴파일 타임에 Type 지정 가능
        // IDE에서 입력해보면 obj. 까지 치면 Person의 멤버들이 출력되는 것을 볼 수 있다.
    }
}