인프런 타입스크립트 기초를 통해 공부중이며 제로초님의 유료강좌입니다.

코드를 통으로 가져오지는 않겠습니다.(내가 글을 쓰면서 복습하는게 목적이기때문에 필요한 부분만)

 

타입스크립트에 익숙하지않다면 자바스크립트로 먼저 코드를 작성하고 그 후에 타입스크립트로 변환하도록하자.

 

타입스크립트 (Typescript)

 

클래스와 인터페이스를 사용할때 언제 써야할까?

만약 내가 작업할 때 new키워드를 쓰지 않고 상속의 구현만 할것이라면 인터페이스를 사용해도 무방하다.

(실제 사용은 클래스에서 extends 하는 방식으로 하고 구현만 할거라면 인터페이스 implements해준다.

interface Example{
    (a: number, b: number) : number;
}

const ex: Example = (a,b) => a + b;

함수나 클래스는 할당되는 문법이 따로 있기 때문에 보통 객체에서 인터페이스를 많이 쓴다고 한다.(취향일수도)

 

 

인터페이스로 객체 타입을 선언

interface Example{
    add: (a: number, b: number) => number;
}

const ex: Example = {
    add: (a,b) => {
        return a + b;
    }
}

 

 

제네릭(Generic)

인터페이스에서의 제네릭

// 인터페이스
interface Test<T>{
	//...
}

// 클래스
class Test<T> implements Example{ 
	//...
}

// 함수
function Test<T>(arr: T[], callback: (item: T) => void): void{
	//...
}

 

 

 

타입 여러개를 더하기하는 함수를 만들고 싶다고 했을때 어떻게 해야할까?

(아래 예제에 string 타입을 더하고 있는 함수하나가 있고 string과 number타입을 모두 포함하는 함수를 만들고 싶다. 그리고 같은 타입끼리만 더해져야한다.)

function add(a: string, b: string): string{
    return a + b;
}

두번 선언할 것인가?

자바스크립트에서는 같은 함수를 두 번 선언할 수 없다.

// (X)
function add(a: string, b: string): string{
    return a + b;
}

function add(a: number, b: number): number{
    return a + b;
}

이렇게 선언한다면 에러가 난다. 그렇다면 or 연산자를 써서 해결해야할까?

function add(a: number | string, b: number | string): number | string{
    return a + b;
}

타입 두개를 추가하는데에는 만족했지만 해당 타입끼리 더해야한다는 요구조건을 만족할 수 없다.

아래와 같이 string과 number의 더하기도 허용한다.

add(1 + 'abc');

이때 해결할 수 있는 방법은 제네릭을 사용하는것이다.

제네릭은 임의의 변수를 만들어서 함수를 생성하고 나중에 이 함수를 사용할때 타입값을 입력받는 것이다.

 

함수로는 생성할 수 없고 인터페이스를 통해 생성해야한다.

// (X)
function add<T>(a: T, b: T): T{
    return a + b;
}

// (O)
interface obj<T>{
    add: (a: T, b: T) => T;
}

 

인터페이스 선언 시에 T라는 임의의 타입을 작성해 둠으로써 여유롭게 선언한다.

(T는 임의의 이름이다. 다른 이름도 상관 없다)

// T라는 타입을 제네릭으로 바꾼다.
interface obj<T>{
    add: (a: T, b: T) => T;
}

// 인터페이스를 사용할때 타입을 정해준다.
// <T>에 number를 넣게되는 것
const a: obj<number> = {
    add: (a, b) => (a + b);
};

// <T>에 string을 넣게되는 것
const b: obj<string> = {
    add: (a, b) => (a + b);
};

a.add(1, 2);
b.add('a', 'b');

 

addEventListener를 사용할 때 보통 아래와 같이 사용하는데, 

타입스크립트가 어떤식으로 동작하는지 알기 위해 lib.dom.d.ts파일을 확인해보자.

document.addEvenListener<'submit'>('submit', () => {});

lib.dom.d.ts에는 <K extends keyof ...... 라고 적혀있다.

이때 저 K가 제네릭이다.

 

실제 내가 넣었던 'submit'이 K로 들어가게된다.

그리고 extends keyof DocumentEventMap을 확인한 뒤 DocumentEventMap -> ... -> 순서대로 따라가다보면 GlobalEventHandlerEventHandlersEventMap까지 다다를 수 있는데,

내부에 key를 확인해보면 된다.

key에 없는 값을 입력할 경우 타입스크립트에서는 유추할 수 없다.

만약 타입이 현저히 다를 경우 두번째 documentEventListener(type: string.... 선언에 맞춰간다.

(둘 중에 적합한 것을 찾는다)

이것을 function overloading이라고 한다.

 

제네릭에서 extends란

클래스에서 extends는 상속을 말하는데 타입스크립트에서 extends는 제한이라는 뜻이다.

즉 타입에 대한 제한을 두는 것이다.

아래 예시는 boolean을 타입으로 넣는 경우이다.

interface obj<T>{
    add: (a: T, b: T) => T;
}

const a: obj<boolean> = {
    add: (a, b) => (a + b);
};

boolean타입을 더한다니? 이렇듯 아무거나 타입을 넣게되면 원하는 결과가 나오지 않기 때문에 제한을 둔다.

만약 extends string을 넣는다면 string과 string을 extends한 타입들만 선언할 수 있도록 하는 것이다.

interface obj<T extends string>{
    add: (a: T, b: T) => T;
}

여기서 number를 넣게되면 에러가 난다.

 

아래처럼 작성된 경우 K는 HTMLElementEventMap에서 keyof로 제한을 두겠다는 의미다.

첫번째 인수로는 key로 포함된 이벤트만 들어와야 에러가 나지 않는다.

document.addEventListener<'submit'>('submit', 

 

제네릭에 기본 값을 넣을 수 있다.

// K가 넘겨오는 타입 extends는 제약사항
// HTMLElementTagNameMap에서 key를 제약으로둔다.
// = 'div'는 기본 값
createElement<K extends keyof HTMLElementTagNameMap = 'div'>(.....

 

+ 제네릭 안에 제네릭도 들어갈 수 있다.

 

 

lib.dom.d.ts파일의 find를 확인해보자.

이것은 인터페이스 내부에 들어있다.

아래와 같이 선언했을때

Array<T>에서 먼저 타입을 받고 있다.

그리고 Array로 'a', 'b', 'c'를 삽입했기 때문에 item의 타입이 string인 것을 타입스크립트는 유추할 수 있다.

['a','b','c'].forEach((item) => {});

 

만약 오만가지 타입을 다 넣는다면 any가 될까?

아니다. 

// item : string | number | boolean | undefined | null 형식으로 유추한다.
['a',1,true, undefined, null].forEach((item) => {});

 

빈 배열을 삽입할 경우에는 never가 된다.

[].find((item) => {
    //......
});

 

아래 find에는 predicate라는 텍스트가 있는데

true, false를 반환하는 콜백함수인 경우에 predicate라 부른다.  

 

forEach를 구현하여 제네릭에 대해 더 자세히 알아보자.

function forEach<T>(arr: T[], callback: (item: T) => void): void{
    for(let i: number = 0; i < arr.length; i++){
        callback(arr[i]);
    }
}

// 사용할때 제네릭 넣기
// item이 자동으로 유추된다.
// forEach 선언할 때 item의 타입에도 T를 넣었기 때문에 유추가 되는 부분이다.
forEach<string>(['a', 'b', 'c'], (item) => {
     //........
});

 

일단 타입스크립트에서 타입추론되는 부분이 있다면 그 중에 제네릭일 확률이 있고 콜백함수에서 타입추론되는 경우 제네릭일 확률이 높다고한다.

 

 

 

 

+ Recent posts