Generic

김명주·2023년 5월 21일
0
post-custom-banner

Generic?

Generic은 타입을 정하지 않고 여러 타입을 사용할 수 있게 해준다.
즉, 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다. 한번의 선언으로 다양한 타입에 '재사용'이 가능하다는 장점이 있다.
제네릭을 쓰지 않을 경우, 불필요한 타입 변환을 하기 때문에 프로그램의 성능에 악영향을 미치기도 하는데, 제네릭을 사용하게되면 따로 타입 변환을 할 필요가 없어서 프로그램의 성능이 향상되는 장점이 있다.

Typescript의 Generic 문법은 함수와 클래스, 인터페이스에서 사용할 수 있다.

함수에서의 Generic

<> 괄호를 이용하여 선언할 수 있다. <>안에 선언된 T는 일종의 매개변수처럼 타입 정보를 가지고 있으며 이 타입 정보로 매개변수의 타입을 결정할 수 있다.
<>안에는 어떠한 단어도 들어올 수 있다.
알 수 없었던 첫번째 매개변수의 타입이 Generic 문법을 통해서 타입이 정해지게 된다. 아래 예시처럼 toArray('neo', 123) 의 첫번째 인자는 string이고 두번째 인자는 number인데 오류가 발생하는걸 볼 수 있다.

그 이유는 첫번째 인자의 타입이 string이기 때문에 Generic 문법을 통해서 타입 추론을 하게되어 function toArray<T>(a:T, b:T)function toArray(a:string, b:string)으로 되었다고 볼 수 있다.

interface Obj{
    x:number
}

type Arr = [number, number]

// <> 괄호를 이용하여 선언
// <>안에 선언된 T는 일종의 매개변수처럼 타입 정보를 가지고 있다.
// 이 타입 정보로 매개변수의 타입을 결정할 수 있다.
function toArray<T>(a:T, b:T){
    return [a, b]
}


console.log(
    toArray('neo', 'anderson'),
    toArray(1,2),
    toArray(true,false),
    toArray({x:1},{x:2}),
    toArray([1,2],[3,4]),
  	toArray('neo', 123),// 'number' 형식의 인수는 'string' 형식의 매개 변수에 할당될 수 없습니다
  	// 또는 이렇게 명시적으로 T의 타입을 지정하는 방법도 있다.
  	toArray<string>('kim','park')
)

class에서의 Generic

class에선 아래와 같이 사용할 수 있다.

class User<P>{
    public payload: P
    // payload의 타입을 지정해 주지 않으면 
    // 'payload' 매개 변수에는 암시적으로 'any' 형식이 포함됩니다. 오류 발생

    // 클래스에 Generic을 사용하여 payload의 타입을 지정해 줄 수 있다.
    constructor(payload : P){
        this.payload = payload
    }
    getPayload(){
        return this.payload
    }
}

위의 예제를 좀 더 축약하면 아래와 같이 작성할 수 있다.

class User<P>{    
    constructor(public payload : P){
    }
    getPayload(){
        return this.payload
    }
}

class를 생성자 함수를 통해 호출할 때

class를 생성자 함수를 통해 호출할 때에는 아래와 같이 사용할 수 있다.

<>를 생성자 함수를 호출할 때 사용하면 된다. 이렇게 되면 UserAType이라는 인터페이스의 내용이 <P>에 들어가게 되고 <P>의 타입이 UserAType이 된다.
만약 User라는 이름의 하나의 class를 사용할 때마다 다른 타입의 데이터를 만들고 싶다면 Generic을 사용해 타입을 필요에 따라 유연하게 사용할 수 있다.

class User<P>{    
    constructor(public payload : P){
    }
    getPayload(){
        return this.payload
    }
}

interface UserAType{
    name:string
    age:number
    isValid:boolean
}

interface UserBType{
    name:string
    age:number
    emails:string[]
}
const user =  new User<UserAType>({
    name:'kim',
    age:23,
    isValid:true,
    emails:[] // 개체 리터럴은 알려진 속성만 지정할 수 있으며 'UserAType' 형식에 'emails'이(가) 없습니다
})

interface에서의 Generic

마찬가지로 이름 뒤쪽에 선언할 수 있다. 아래와 같이 const dataA:MyData<string>부분에 선언된 것 처럼 string으로 명시되어 있다면 value의 값은 string으로, number라면 number로 들어가야 한다.

interface MyData<T>{
    name:string
    value:T // 이 값의 타입은 T의 타입을 따라가게 된다.
}

const dataA:MyData<string> = {
    name:'Data A',
    value:123 // 'number' 형식은 'string' 형식에 할당할 수 없습니다.
}

const dataB:MyData<number> = {
    name:'Data B',
    value:1
}

const dataC:MyData<boolean> = {
    name:'Data C',
    value:true
}

const dataD:MyData<number []> = {
    name: 'Data D',
    value: [1,2,3,4]
}

제약조건

인터페이스의 제네릭에 조건을 달고 싶다면 extends 키워드를 사용하면 된다.
아래와 같이 사용하게 되면 T의 타입은 string 이거나 number 타입만 허용한다는 의미가 된다.

interface MyData<T extends string | number>{
    name:string
    value:T
}

const dataA:MyData<string> = {
    name:'Data A',
    value:'Hello World'
}

const dataB:MyData<number> = {
    name:'Data B',
    value:1
}

const dataC:MyData<boolean> = { // 'boolean' 형식이 'string | number' 제약 조건을 만족하지 않습니다
    name:'Data C',
    value:true
}

const dataD:MyData<number []> = { // 'number[]' 형식이 'string | number' 제약 조건을 만족하지 않습니다
    name: 'Data D',
    value: [1,2,3,4]
}
profile
개발자를 향해 달리는 사람
post-custom-banner

0개의 댓글