타입스크립트 기초 - 18

Stulta Amiko·2022년 7월 29일
0

타입스크립트 기초

목록 보기
18/24
post-thumbnail

제네릭 타입 이해하기

제네릭 타입은 인터페이스나 함수 타입 별칭등에 사용할 수 있는 기능이다.
해당 심벌의 타입을 미리 지정하지 않고 다양한 타입에 대응하려고 하는 기능이다.

제네릭 사용하기

IValuable.ts

export interface IValuable<T>{
    value: T
}

index.ts

import { IValuable } from "./IValuable";

export class Valuable<T> implements IValuable<T>{
    constructor(public value: T) { }
}

export {IValuable}

printValue.ts

import { Valuable,IValuable } from "./index";

export const printValue = <T>(o: IValuable<T>): void => console.log(o.value)
export {Valuable,IValuable}

value-test.ts

import { printValue,Valuable } from "./printValue";

printValue(new Valuable<number>(1))
printValue(new Valuable<boolean>(true))
printValue(new Valuable<string>('hello'))
printValue(new Valuable<number[]>([2,3,4]))

실행결과
1
true
hello
[ 2, 3, 4 ]

위와 같은 형식으로 제네릭을 사용할 수 있다.

코드가 길어 보여서 뭔가 복잡해보이는데 별거아니다

그리고 타입스크립트는 저런식으로 제네릭안에 타입을 넣지않더라도
타입추론을 통해서 알아서 작동하긴 한다.

import { printValue,Valuable } from "./printValue";

printValue(new Valuable(1))
printValue(new Valuable(true))
printValue(new Valuable('hello'))
printValue(new Valuable([2,3,4]))

실행결과
1
true
hello
[ 2, 3, 4 ]


제네릭 타입 예약

제네릭 예약은 타입 변수에 적용할 수 있는 타입의 범위를 한정하는 기능을 한다.
타입스크립트에서 제네릭 함수의 타입을 제한하고 싶을때는 다음과 같은 구문을 사용한다.

<최종타입1 extends 타입1, 최종타입2 extends 타입2>(a: 최종타입1,b: 최종타입 2,...){}

printValueT.ts

import { IValuable } from "./IValuable";

export const printValueT = <Q,T extends IValuable<Q>>(o: T) => console.log(o.value)

export {IValuable}

printValueT-test.ts

import { printValueT,IValuable } from "./printValueT";
import { Valuable } from "./printValue";

printValueT(new Valuable(1))
printValueT({value: true}

실행결과
1
true

매개변수 타입을 어느 방식으로 제약하느냐만 다르고 사용방법을 동일하다.


new 타입제약

const create = <T>(type: T): T => new type()

위코드는 오류가 발생한다.
타입스크립트에서 타입의 타입을 허용하지 않기 때문이다.

그래서 타입의 타입을 발생시키는것 보다는 다음과 같이 사용할 수 있다.

const create = <T extends {new(): T}>(type: T) => new type()

혹은 중괄호를 생략해서 더 간결하게 표현 할 수도 있다.

const create = <T>(type: new() => T):T => new type()

결론적으로 {new(): T}와 new() => T 는 같다는 의미가 된다.
new 연산자를 type에 적용하면서 type의 생성자 쪽으로 매개변수를 전달해야 할 때 다음처럼 new(...args) 구문을 사용한다.

const create = <T>(type: {new(...args):T},...args): T => new type(...args)

create.ts

export const create = <T>(type: {new(...args): T}, ...args):T => new type(...args)

create-test.ts

import { create } from "./create";

class Point {constructor(public x: number, public y: number) { }}
[
    create(Date),
    create(Point,0,0)
].forEach(s=>console.log(s))

실행결과
2022-07-29T07:59:39.557Z
Point { x: 0, y: 0 }

이런식으로 잘 작동하는 모습을 볼 수 있다.


인덱스 타입제약

객체의 일정속성만 추려서 더 단순한 객체를 만들려고 할때
다음과 같이 일부속성만 추출해서 간단한 형태로 만들 수 있다.

const obj = {name: 'Jane', age: 22, city: 'Seoul', country: 'Korea'}
pick(obj,['name','obj'])

위 pick 함수는 다음과 같이 구현할 수 있다.

pick.ts

export const pick = (obj,keys) => keys.map(key => ({[key]: obj[key]}))
    .reduce((result,value) => ({...result,...value}), {})

pick-test.ts

import { pick } from "./pick";

const obj = {name: 'Jane', age: 22, city: 'Seoul', country: 'Korea'}
console.log(
    pick(obj,['name','age']),
    pick(obj,['nam','agge'])
)

실행결과
{ name: 'Jane', age: 22 } { nam: undefined, agge: undefined }

이런식으로 key를 잘못입력하는 경우 원하지 않는 값이 나오게 된다.
이런일을 방지하기 위해서 인덱스 타입제약을 사용할 수 있다.

위에서 구현한 pick 함수와 다르게 T와 K라는 타입변수를 적용해서 코드를 짜면

다음과 같은 오류가 발생하게 된다.

이 오류메시지를 해결하기 위해서는 K가 T의 key라는것을 알려줘야 한다.
이때 타입스크립트의 인덱스 타입 제약을 사용한다.

오류가 사라진 모습을 볼 수 있다.


그러면 이제 위와 같이 못된 입력을 미리 방지할 수 있게 된다.

0개의 댓글