TypeScript Generic 중

Generics

Type Script Generic 파트 번역본

Index

  1. Introduction

  2. Hello World of Generics

  3. Working with Generic Type Variables

  4. Generic Types

  5. Generic Classes

  6. Generic Contraints

Introduction

소프트웨어 엔지니어링의 중요한 부분은 잘 정의되었을 뿐만아니라 일관된 된 API들을 가진 컴포넌트를 만드는것 뿐만 아니라, 그 컴포넌트들이 재사용 가능해야 한다는 것이다. 내일의 데이터 뿐만아니라 오늘의 데이터에서 작동할 수 있는 컴포넌트는 대규모 소프트웨어를 만들기 위해 가장 유연한 능력을 줄 것이다. C#이나 Java 와 같은 언어에서와 같이, 재사용가능한 컴포넌트를 만들기위해 툴박스에서 중요한 툴중 하나는 바로, 단순히 하나의 타입에서만 작동하는 것이 아니라 여러 타입에서 작동하는 컴포넌트를 만둘스 있게 해주는 Generics이다. Generics는 사용자로 하여금 컴포넌트를 소비하고 그들의 타입을 사용할 수 있게 허락한다.

Hello World of Generics

시작하기 위해 Identity function을 활용해 hello world를 제네릭으로 만들어보자. (Identity function이란 인자값으로 넘겨진 것을 고스런히 반환하는 함수를 말한다. echo 커멘드와 비슷하게 작동한다고 생각하면 된다.)

제네릭 없이는 우리는 직접 인자값에 타입을 지정해 주거나

function identity(arg: number): number {
    return arg;
}

또는 우리는 Identity function을 any타입으로 구현할 수 있다.

function identity(arg: any): any {
    return arg;
}

any타입역시 모든 타입을 받아들일 수 있기 때문에 Generic 하다고 말할 수는 있지만, 함수가 반환되었을때, 반환된 값이 어떤 타입인지를 놓치게 된다. 숫자를 인자값으로 넘겼을 때, 오직 any 타입이 반환된다는 것만을 알 수 있다.

대신, 어떤 값이 반환이 될 것인지를 인자값을 통해 명시할 방법이 필요하다. 여기서 특별한 변수를 사용할 것인데, 이 값대신 타입에서 작동한다.

function identity<T> (arg: T): T {
    return arg;
}

타입변수 T를 identity함수에 추가했다. 이 T는 사용자가 인자값으로 제공하는 타입을 캡쳐해 우리가 그 정보를 나중에 사용할 수 있게 해준다. 여기서 T를 다시 리턴타입에 사용하였다. 이를통해 우리는 인자값과 리턴값이 동일하다는 사실을 알 수 있다. 이것은 어떤 타입이 들어가는지와 나가는 타입의 정보를 다룰 수 있게 해준다. 이런 identity function은 모든 타입과 작동하기때문에 generic하다고 말한다. any를 사용했던 첫번째 identity 함수와는 달리 Generic을 쓴 함수는 어떤 정보도 놓치지 않아 정교해진다.

한번 identity 함수를 쓰면, 우리는 두가지 방식으로 이것을 호출할 수 있다. 첫번째 방법은 타입 인자값과 함께 모든 인자값들을 함수에 넘기는 것이다.

let output = identity<string>("myString"); // type of output will be 'string'

여기서 명시적으로 T를 string을 함수호출의 하나의 인자값으로써 세팅한다. (() 표현 대신에 <>를 사용하여 표기한다.) 2번째 방법이 가장 일반적인 방법일 것이다. 여기서, 타입 인자값 암시를 사용한다. 즉, 우리는 컴파일러가 자동으로 인자값의 타입에 따라 T를 해석하게 한다.

let output = identity("string");

여기서 명시적으로 <>에 타입을 지정해줄 필요가 없다는데에 주목할 필요가 잇다. 컴파일러는 인자값으로 넘어간 값이 string이라는 것을 보고 T를 string으로 자동으로 세팅해준다. type inference를 사용하는 것이 코드를 읽기쉽고 짧게 유지보수하는데 유리할 수 있지만, 더 복잡한 상황에서 컴파일러가 타입을 읽지 못할경우 명시적으로 타입을 입력해야 할 수도 있다.

Working with Generic Type Variables

제네릭을 사용하기 시작할 때, 위의 예제에서 identity함수와 같은 generic을 만들려고 할 때, 컴파일러가 강제적으로 함수의 바디에서 generic으로 지정된 인자값의 타입의 사용하도록 강제한다는 점에 주목해야 한다. 즉, 당신은 이런 인자값들을 마치 any나 all 타입인 것처럼 다룰 수 있다.

이전에 사용했던 identity 함수를 보자.

function identity<T> (arg: T): T {
    return arg;
}

만약 console.log의 인자값의 길이를 출력할려고 하면 어떻게 될 것인가?

function loggingIdentity<T> (arg: T): T {
    console.log(arg.length); // Error: T doesn't have .length
    return arg;
}

위의코드를 실행하려고 했을때 컴파일러는 에럴를 발생시키는데, .length를 어디에서도 찾을 수 없기 때문이다.
타입변수는 모든 값을 포함해서, 누군가는 .length 속성이 없는 number타입을 인자값으로 넘길 수 있다는 점을 기억해야 한다.

이 함수에서 T를 직접쓰는 것이 아니라 T의 배열을 인자값의 타입으로 쓴다고 가정해보자.

인자값으로 배열을 넘겼기 때문에 .length가 사용할 수 있어야 한다.
다른 타입의 배열을 만들듯 우리는 generic을 구현할 수 있다.

function loggingIdentity<T> (arg: T[]): T[] {
    console.log(arg.length);
    return arg;
}

위의 loggingIdentity함수를 통해 이 함수가 type variables로 T를 받고 인자값으로 T의 배열을 받으며 반환값으로 T의 배열을 받는다는 것을 알 수 있다. 만약 인자값으로 수의 배열을 넘긴다면 반환값으로 마찬가지로 숫자의 배열을 받을 것이다. 이런 작동은 우리에게 훌륭한 유연성을 제공하면서 우리가 다루고 있는 타입들의 부분으로써 generic type variable을 사용 할 수 있게 허락해 준다. 아마 다른 언어에서 비슷한 스타일을 봤을 수도 있다. 다음 섹션에서는 우리는 어떻게 고유의 generic 타입을(e.g. Array) 만들어 낼 수 있는지 알아볼 것이다.

2 Likes