본문 바로가기

Javascript/TypeScript

[타입스크립트 튜토리얼 번역] generics type tutorial | 제네릭타입

https://www.tutorialsteacher.com/typescript/typescript-generic

 

TypeScript Generics

TypeScript - Generics In this section, we will learn about generics in TypeScript. When writing programs, one of the most important aspects is to build reusable components. This ensures that the program is flexible as well as scalable in the long-term. Gen

www.tutorialsteacher.com

해당 문서를 번역한 글입니다. 


더보기

In this section, we will learn about generics in TypeScript.

When writing programs, one of the most important aspects is to build reusable components.

This ensures that the program is flexible as well as scalable in the long-term.

Generics offer a way to create reusable components.

Generics provide a way to make components work with any data type and not restrict to one data type.

So, components can be called or used with a variety of data types.

Generics in TypeScript is almost similar to C# generics.

 

이번 세션에서 우리는 타입스크립트에서의 제네릭에 대해서 배워볼 것입니다. 

프로그램을 작성할 때, 가장 중요한 측면 중 하나는 컴포넌트의 재사용가능성입니다. 

이것은 프로그램이 긴 기간동안 확장가능하고, 유연하도록 보장해줍니다.

그런 점에서, 제네릭은 재사용가능한 컴포넌트들 만들수 있게 해줍니다. 

제네릭은 컴포넌트가 어떤 데이터 타입과도 작동할 수 있고, 또한 단순한 하나의 타입에만 제한되지 않도록 방법을 제공합니다. 

그래서, 컴포넌트들은 다양한 데이터타입들과 함께 호출되거나, 사용될 수 있습니다. 

타입스크립트에서의 제네릭은 C#에서의 제네릭과 거의 유사합니다. 


Let's see why we need Generics using the following example.

다음의 예제를 보면서, 제네릭이 왜 필요한지 알아봅시다. 

function getArray(items : any[] ) : any[] {
    return new Array().concat(items);
}

let myNumArr = getArray([100, 200, 300]);
let myStrArr = getArray(["Hello", "World"]);

myNumArr.push(400); // OK
myStrArr.push("Hello TypeScript"); // OK

myNumArr.push("Hi"); // OK
myStrArr.push(500); // OK

console.log(myNumArr); // [100, 200, 300, 400, "Hi"]
console.log(myStrArr); // ["Hello", "World", "Hello TypeScript", 500]

 

 

더보기

In the above example, the getArray() function accepts an array of type any.

It creates a new array of type any, concats items to it and returns the new array.

Since we have used type any for our arguments, we can pass any type of array to the function.

However, this may not be the desired behavior.

We may want to add the numbers to number array or the strings to the string array but not numbers to the string array or vice-versa.

 

To solve this, TypeScript introduced generics.

Generics uses the type variable , a special kind of variable that denotes types.

The type variable remembers the type that the user provides and works with that particular type only.

This is called preserving the type information.

The above function can be rewritten as a generic function as below.

 

위의 예시를 보면, getArray()함수는 어떤 타입의 배열이든지 받아줍니다. 

이것은 any 타입의 새로운 배열을 들고, items를 합칩니다. 그리고 새로운 배열을 반환합니다. 

우리가 인자에 any타입을 사용했기 때문에, 우리는 any타입의 배열을 함수에 넣을 수 있습니다. 

하지만, 이것은 아마도 타입스크립트에서 권장되지 않는 행동일 것입니다. 

우리는 아마도 숫자를 숫자 배열에 넣거나, 스트링을 스트링 배열에 넣기를 원하지, 

숫자를 스트링배열에 넣는다거나 하는 행위를 원하지 않을 것입니다. 

 

이것을 해결하기 위해서, 타입스크립트는 제네릭을 소개했습니다. 

제네릭은 타입을 나타내는 특별한 타입 변수<T>를 사용합니다. 

타입 변수는 유저가 제공한 타입을 기억하고,  제공된 그 타입하고만 작동을 합니다. 

이것을 타입정보보존이라고 부릅니다. 

위의 함수는 아래처럼 제네릭 함수를 통해  재작성할 수 있습니다.  

 

function getArray<T>(items : T[] ) : T[] {
    return new Array<T>().concat(items);
}

let myNumArr = getArray<number>([100, 200, 300]);
let myStrArr = getArray<string>(["Hello", "World"]);

myNumArr.push(400); // OK
myStrArr.push("Hello TypeScript"); // OK

myNumArr.push("Hi"); // Compiler Error
myStrArr.push(500); // Compiler Error

 

더보기

In the above example, the type variable T is specified with the function in the angle brackets getArray<T>.

The type variable T is also used to specify the type of the arguments and the return value.

This means that the data type which will be specified at the time of a function call,

will also be the data type of the arguments and of the return value.

We call generic function getArray() and pass the numbers array and the strings array.

For example, calling the function as getArray([100, 200, 300]) will replace T with the number and so,

the type of the arguments and the return value will be number array.

In the same way, for getArray(["Hello", "World"]), the type of arguments and the return value will be string array.

So now, the compiler will show an error if you try to add a string in myNumArr or a number in myStrArr array. Thus, you

get the type checking advantage.

 

위의 예시를 보면, 타입변수 T는 꺽쇠괄호 getArray<T>라는 함수로  특정지어지고 있습니다. 

또한 저 타입변수 T는 인자의 타입과 반환값의 타입도 특정하기 위해서 사용됩니다. 

이는 함수가 호출될 때 지정되는 데이터 타입이, 곧 인자와 반환값의 타입이 될 것이라는 것을 의미합니다.

우리는 getArray()라는 제네릭 함수를 호출하고, 숫자배열과 스트링 배열을 넣어주고 있습니다. 

예를 들어서, 함수를 getArray<number>([100, 200, 300])과 받은 방식으로 호출하는 것은 T를 숫자와 대체할 것이고, 

그렇게함으로써 인자의 타입과 반환값의 타입은 숫자 배열이 될 것입니다. 

같은 방법으로, getArray(["Hello", "World"]) 또한, 인자와 반환값의 타입은 스트링 배열이 될것입니다. 

그래서 이제, 컴파일러는 보여줄 것입니다 에러를 만약에 당신이 스트링을 숫자 배열에 넣거나 스트링 배열에 숫자를 넣으면. 그러므로 여러분은 이제 타입을 확인하는 이점을 얻을 수 있게 된 것입니다. 

 

 

더보기

It is not recommended but we can also call a generic function without specifying the type variable.

The compiler will use type inference to set the value of T on the function based on the data type of argument values.

 

권장되지 않는 방법이긴하지만, 우리는 제네릭 함수를 특정 타입변수를 선언하지않고 선언할 수 있습니다. 

그러면 컴파일러는 인자에 들어온 데이터를 기반으로 T의 타입을 설정하기 위해서 타입추론을 사용할 것입니다.

 

let myNumArr = getArray([100, 200, 300]); // OK
let myStrArr = getArray(["Hello", "World"]); // OK

Generics can be applied to the function's argument, a function's return type, and a class fields or methods.

제네릭은 함수의 인자, 함수의 반환값, 클래스 필드와 메소드에도 적용될 수 있습니다. 


Multiple Type Variables(다수의 타입 변수들)

 

We can specify multiple type variables with different names as shown below.

밑에서 보시는바와 같이, 우리는 다수의 타입 변수들을 다른 이름으로 특정할 수 있습니다. 

function displayType<T, U>(id:T, name:U): void { 
  console.log(typeof(id) + ", " + typeof(name));  
}

displayType<number, string>(1, "Steve"); // number, string

 

Generic type can also be used with other non-generic types.

제네릭 타입은 제네릭이 아닌타입과도 함께 사용될 수 있습니다. 

function displayType<T>(id:T, name:string): void { 
  console.log(typeof(id) + ", " + typeof(name));  
}

displayType<number>(1, "Steve"); // number, string

Methods and Properties of Generic Type(제네릭타입의 속성과 메소드)

When using type variables to create generic components, TypeScript forces us to use only general methods which are available for every type.

제네릭 컴포넌트를 만들기 위해서 타입변수를 이용할 때, 타입스크립트는 모든 타입에 대해서 이용가능한 메소드만 사용하도록 우리로 하여금 강제합니다.

function displayType<T, U>(id:T, name:U): void { 
    
    id.toString(); // OK
    name.toString(); // OK

    id.toFixed();  // Compiler Error: 'toFixed' does not exists on type 'T'
    name.toUpperCase(); // Compiler Error: 'toUpperCase' does not exists on type 'U'

    console.log(typeof(id) + ", " + typeof(name));  
}

 

더보기

In the above example, id.toString() and name.toString() method calls are correct because the toString() method is available for all types.

However, type specific methods such as toFixed() for number type or toUpperCase() for string type cannot be called.

The compiler will give an error.

You can use array methods for the generic array.

 

위의 예제를 보면, id.toString() 과 name.toString() 는 바르게 사용했습니다. 왜냐하면 toString()메서드는 모든 타입에 대해서 이용가능하기 때문입니다. 

그러나 타입이 특정한 메서드인, 예를 들어서 toFixed()는 넘버 타입을 위한 것이고, toUpperCase()는 문자열타입을 위한 것인데, 이런 것들은 호출될 수 없습니다. 

컴파일러는 에러를 내뱉을 것입니다. 

당신은 배열 배열 메서드를 일반적인 배열에서 사용할 수 있습니다. 

 

function displayNames<T>(names:T[]): void { 
    console.log(names.join(", "));  
}

displayNames<string>(["Steve", "Bill"]); // Steve, Bill

So, be careful while using generic types and calling type specific methods or properties.

그래서, 제네릭 타입을 사용하면서, 특정한 타입에 적용되는 메서드나 속성을 사용하려고 할 때는 조심해야 합니다. 

 

Generic Constraints(제네릭 제한)

As mentioned above, the generic type allows any data type.

위에서 언급했듯이, 제네릭 타입은 아무 데이터 타입이나 혀용합니다. 

 

However, we can restrict it to certain types using constraints. Consider the following example:

그러나 우리는 constraints를 이용해서 명확한 타입으로 제한할 수 있습니다. 다음의 예시를 살펴봅시다. 

class Person {
    firstName: string;
    lastName: string;

    constructor(fname:string,  lname:string) { 
        this.firstName = fname;
        this.lastName = lname;
    }
}

function display<T extends Person>(per: T): void {
    console.log(`${ per.firstName} ${per.lastName}` );
}
var per = new Person("Bill", "Gates");
display(per); //Output: Bill Gates

display("Bill Gates");//Compiler Error

 

더보기

In the above example, the display function is a generic function with constraints.

A constraint is specified after the generic type in the angle brackets.

The constraint <T extends Person> specifies that the generic type T must extend the class Person.

So, the Person class or any other class that extends the Person class can be set as generic type while calling

the display function, otherwise the compiler will give an error.

 

위의 예시를 보면, display 함수는 constrains를 가진 제네릭 함수입니다. constraint는 꺽쇠기호 안에서 제네릭 타입 다음에 특정됩니다.  지금 constraint 은 제네릭 타입 T가 반드시 Person이라는 클래스를 확장해야한다고 명시하고 있습니다. 

그래서, display function을 호출하는 동안에,  Person 클래스 또는 Person을 확장한 어떤 다른 클래스든지 제네릭 타입으로 설정될 수 있습니다. 그렇지 않는 경우에는 콤파일러가 에러를 낼 것입니다.