코딩/한 입 크기로 잘라먹는 타입스크립트

[TypeScript] 제네릭 함수와 타입 변수

김기지 2023. 9. 13. 23:26

🐼 제네릭

함수나 인터페이스, 타입 별칭, 클래스 등을 다양한 타입과 함께 동작하도록 만들어 주는 기능

매개변수를 받아 그대로 반환하는 함수를 만들어보자.

function func(value: any) {
  return value;
}

let num = func(10);
// any 타입

let str = func("string");
// any 타입

num은 숫자 타입을, str은 문자열 타입의 값을 가지지만 커서를 대보면 모두 any타입이 된다. 이는 func 함수의 반환값이 return 문을 기준으로 추론되었기 때문이다.

이러한 경우 각 변수는 any 타입이기 때문에 num.toFixed 와 같이 숫자 타입에 string 타입의 메소드를 사용해도 타입스크립트가 오류를 감지하지 못하게 된다. 이 코드는 결국 실제 실행할 시 런타임 오류를 발생시켜 아주 위험하다.

 

그렇다면 모든 변수에 타입 좁히기를 이용해 사용해야 할까?

 

이렇게 인수로 전달할 값의 타입에 따라 반환값의 타입이 변하도록 할 때 제네릭을 이용한다.

 

 

 

 

 

 

🐼 제네릭 함수

제네릭(Generic)
일반적인, 포괄적인

모든 타입의 값을 다 적용할 수 있는 범용적인 함수이며 함수의 인수에 따라 반환값의 타입을 가변적으로 만들 수 있다.

 

📍 제네릭 함수 선언하기

// 제네릭 함수
// T -> 타입 변수(타입을 담음)
function func<T>(value: T): T{
    return value;
};
  1. 함수 이름 뒤에 꺽쇠를 열고 타입 변수 T를 선언
    • 타입 변수 -> 타입을 담는 변수
  2. 매개변수와 반환값의 타입을 타입 변수T로 설정
타입 변수T에 어떤 타입이 할당되는 지는 함수가 호출될 때 결정된다.
// 함수를 호출할 때 타입 변수에 할당되는 타입이 인수에 따라 정의됨
let num = func(10); // number 타입
let bool = func(true); // boolean 타입
let str = func('string'); // string 타입

  1. number 타입의 값을 인수로 전달
  2. 매개변수 value에 number타입의 값이 저장
  3. T가 number 타입으로 추론됨
  4. func 함수의 반환값 타입 또한 number 타입이 됨

 

 

 

 

제네릭 함수를 호출할 때 타입 변수에 할당할 타입을 직접 명시할 수 있다.
// 타입 변수에 할당되는 타입을 프로그래머가 명시할 수도 있음
// 명시 ❌ -> 인수로 전달한 number[](숫자 배열) 타입이 됨
let arr1 = func([1, 2, 3]); // number[]타입
// 튜플 타입으로 정의하기
let arr2 = func<[number, number, number]>([1, 2, 3]); // [number, number, number]타입
  1. T에 [number, number, number] 튜플 타입이 할당됨
  2. 매개변수 value와 반환값 타입이 모두 튜플 타입이 됨

타입스크립트는 타입을 추론할 때 항상 일반적이고 좀 더 범용적인 타입으로 추론하기 때문에 타입 변수에 할당하고 싶은 특정 타입이 있다면 함수 호출과 함께 직접 명시해 주는 것이 좋다.

 

 

 

 

 

 

 


🐼 타입 변수 응용하기

타입 변수

인수의 타입을 담는 변수로 인수의 타입을 받아 함수의 반환값의 타입을 결정한다.

 

 

사례 1. 타입 변수 여러개 사용하기

// 타입 변수 여러개 선언
function swap<T, U>(a: T, b:U){
    return [b, a];
};

// 인수의 타입이 다를때 오류 발생 => 타입 변수를 따로 선언
const [a, b] = swap("1", 2);

2개 이상의 인수를 받아오는 경우, 인수의 타입이 같지 않다면 오류가 발생한다. 이를 해결하기 위해 타입 변수를 따로 선언해 사용할 수 있다.

 

 

 

 

 

사례 2. 다양한 배열 타입

// 타입 변수를 사용하면 함수가 호출되기 전까지는 unknown 타입으로 추론함
// -> unknown 타입에 배열 인덱스를 사용하려고 하면 오류가 발생
// => 데이터의 타입을 T[](T 배열)타입으로 설정
function returnFirstValue<T>(data: T[]){
    return data[0];
};

let num = returnFirstValue([0, 1, 2]); // 0
let str = returnFirstValue(['hello', 'mynameis']); // hello

returnFirstValue 함수는 data를 받아 0번째 인덱스에 해당하는 요소를 리턴한다. 이때 data의 타입을 T[]로 설정한다. 

제네릭 함수는 함수가 호출되면 인수의 타입을 받기 때문에 호출되기 전까지는 타입이 존재하지 않아 자동으로 unknown 타입으로 추론된다. 때문에 unknown 타입은 베열 인덱스가 존재하지 않으므로 T[] 타입으로 설정해주어야 한다.

또, data의 타입은 T[]로 설정되었기 때문에 배열이 아닌 값은 인수로 전달할 수 없으며 배열을 인수로 전달하면 T는 배열 요소의 타입으로 할당된다.

 

여기서 만약 유니온 타입의 배열을 전달하면 어떨까?

// 유니온 배열 타입의 인수를 전달한 경우
// (string | number)[] 타입으로 unio의 타입은 string|number 타입
let unio = returnFirstValue([1, 'hello', 'mynameis']); // 1

전달한 인수의 타입은 (string | number)[] 타입으로 unio는 number 타입의 1을 가지고 있음에도 string | number 타입으로 추론된다.

 

unio의 타입을 값에 맞게 설정하기 위해서 첫번째 요소의 타입을 할당받도록 타입 변수를 설정한다.

// 인수가 유니온 타입의 경우
// 첫번째 요소 타입만 타입 변수로 받고 나머지는 unknown배열 타입으로 정의
function returnFirstValue<T>(data: [T, ...unknown[]]){
    return data[0];
};

첫번째 요소의 타입으로 추론하면 되므로 튜플 타입을 사용한다. 첫번째 요소의 타입을 타입 변수로 받고 나머지 요소는 unknown[]로 설정한다.

 

 

 

 

 

사례 3. 타입 변수 제한하기

타입이 문자열, 배열, 객체 등 다양한 인수를 받는 함수를 만들어 보자.

// T의 타입 제한하기
// 인수의 타입이 배열, 문자열, 객체 등으로 다양함
// -> length가 숫자 타입으로 존재하는지 제한하기
// T extends {length: number}
// => length가 숫자 타입으로 존재하는 객체를 확장해 T를 제한함
function getLength<T extends {length: number}>(data: T){
    return data.length;
};

let var1 = getLength([1, 2, 3]); // 3

let var2 = getLength('12345'); // 5

let var3 = getLength({length: 10}); // 10

getLength 함수는 다양한 타입의 인수를 받아 인수의 length를 리턴한다.

따라서 모든 인수는 공통적으로 length 프로퍼티를 가져야 하므로 타입 변수를 "length 프로퍼티를 갖는 객체 타입"으로 제한한다.

타입 변수를 제한할 때는 확장(extends)을 이용한다. 

 

T extends {length: number}

T는 {length: number} 객체 타입의 서브 타입이 된다. 즉, T는 무조건 number 타입의 프로퍼티 length를 가지고 있는 타입이 되어야 한다는 의미이다. 따라서 인수에 length 프로퍼티가 존재하지 않는다면 오류가 발생한다.

getLength("123");            // ✅

getLength([1, 2, 3]);        // ✅

getLength({ length: 1 });    // ✅

getLength(undefined);        // ❌

getLength(null);             // ❌

 

 

 

 


🔗 타입스크립트 강의

 

한 입 크기로 잘라먹는 타입스크립트(TypeScript) - 인프런 | 강의

문법을 넘어 동작 원리와 개념 이해까지 배워도 배워도 헷갈리는 타입스크립트 이제 제대로 배워보세요! 여러분을 타입스크립트 마법사🧙🏻‍♀️로 만들어드립니다., 프론트엔드의 피할 수

www.inflearn.com

 

728x90