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

[TypeScript] 제네릭 인터페이스와 제네릭 타입 별칭

김기지 2023. 9. 15. 22:21

🌿 제네릭 인터페이스

제네릭은 인터페이스에서도 적용이 가능하다.

인터페이스에 타입 변수를 선언해 사용하면 제네릭 인터페이스가 된다.

interface KeyPair<K, V>{
    key: K;
    value: V;
};

 

  • 키페어를 저장하는 객체의 타입을 제네릭 인터페이스로 정의

 

제네릭 인터페이스는 제네릭 함수와는 달리 변수의 타입으로 정의할 때 반드시 꺽쇠와 함께 타입 변수에 할당할 타입을 명시해야 한다.

제네릭 함수는 매개변수에 제공되는 값의 타입을 기준으로 타입 변수의 타입을 추론할 수 있지만 인터페이스는 마땅히 추론할 수 있는 값이 없기 때문이다.

let keyPair: KeyPair<string, number> ={
    key: 'key',
    value: 0,
};

let keyPair2: KeyPair<boolean, string[]> = {
    key: true,
    value: ['1'],
};

변수를 정의할 때 타입 변수에 할당할 타입을 직접 지정하여 정의한다.

  • 변수 keyPair
    • key 프로퍼티는 string 타입
    • value 프로퍼티는 number 타입
  • 변수 keyPair2
    • key 프로퍼티는 boolean 타입
    • value 프로퍼티는 string[] 타입

 

 

 

 

 

 

🌿 인덱스 시그니쳐와 함께 사용하기

객체의 인덱스 시그니쳐 문법과 함꼐 사용하면 굉장히 유연한 객체 타입을 만들 수 있다.

 

인덱스 시그니쳐
프로퍼티의 키와 값의 타입에 관련된 규칙만 만족하면 어떠한 객체든 허용하는 유연한 객체 타입을 만드는 방법
// 인덱스 시그니쳐만 사용한 경우
interface NumberMap {
    [key: string]: number; // 직접 타입을 명시
};

let numberMap: NumberMap = {
    key: -123,
    key2: 123123, // 정의된 타입을 만족하는 값만 받을 수 있음
};

// 제네릭 인터페이스와 사용
interface Map<V> {
    [key: string]: V;
};

// 정의한 타입에 따라 변수의 타입이 바뀜
let booleanMap: Map<boolean> = {
    key: true, 
};

let stringMap: Map<string> = {
  key: "value",
};

key의 타입은 string, value의 타입은 타입 변수 V인 모든 객체 타입을 포함하는 인터페이스를 만든다.

  • 변수 booleanMap
    • 타입을 Map<boolean> 으로 정의
    • -> V 가 boolean 타입이 됨
    • -> key는 string, value는 boolean 타입인 모든 프로퍼티를 포함하는 객체 타입
  • 변수 stringMap
    • 타입을 Map<string> 으로 정의
    • -> V가 string 타입이 됨
    • -> key는 string, value는 string 타입인 모든 프로퍼티를 포함하는 객체 타입

 

 

 

 

 

 

 

 


🌿 제네릭 타입 별칭

인터페이스와 마찬가지로 타입 별칭에도 제네릭을 적용할 수 있다.

type Map2<V> = {
    [key: string]: V;
};

let stringMap2: Map2<string> = {
    key: 'hallo',
};

제네릭 타입 별칭을 사용할 때도 타입으로 정의될 때 반드시 타입 변수에 설정할 타입을 명시해 주어야 한다.

 

 

 

 


🌿 제네릭 인터페이스 활용하기

유저 관리 프로그램 만들기

학생 유저와 개발자 유저를 구분해 유저를 관리하는 프로그램을 구현해보자.

/**
 * 제네릭 인터페이스의 활용 예시
 * 유저 관리 프로그램
 * -> 유저 구분 : 학생 유저 / 개발자 유저
 */

// 두 인터페이스는 서로소 집합
interface Student {
    type: 'student';
    school: string;
};

interface Developer {
    type: 'developer';
    skill: string;
};

interface User {
    name: string;
    profile: Student | Developer;
};

// 학생 유저만 사용하는 함수
function goToSchool(user: User){
    if(user.profile.type !=='student'){
        console.log('잘 못 오셨습니다');
        return;
    };
    const school = user.profile.school;
    console.log(`${school}로 등교 완료`);
};

const developerUser: User = {
    name: '기지',
    profile: {
       type: 'developer',
       skill: 'TypeScript', 
    },
};

const studentUser: User = {
    name: '죠니',
    profile: {
        type: 'student',
        school: '단국대학교',
    },
};

Student 타입과 Developer 타입은 모두 type 프로퍼티를 갖는 서로소 유니온 타입이다.

User 타입은 특정 객체가 학생일 경우 profile 프로퍼티에 Student 타입의 객체가, 개발자일 경우 Developer 타입의 객체가 저장된다.

 

학생 유저만 이용할 수 있는 함수 goToSchool을 선언해 User 타입의 객체를 받아 타입을 좁혀 학생일 때에만 '등교 완료'를 콘솔에 출력하도록 만든다. 

 

이 코드는 지금 당장은 별 문제가 없어 보이지만 유저가 점점 늘어나고 학생만 할 수 있는 기능이 많아진다는 가정을 한다면 매번 기능을 만들기 위한 타입 좁히기가 발생하기 때문에 매우 불편해진다. 게다가 타입을 좁히는 중복 코드가 많이 발생할 것이다.

 

이러한 경우 제네릭 인터페이스를 이용하면 간단하게 구현할 수 있다.

User 인터페이스를 제네릭 인터페이스로 업그레이드 하기

// User 인터페이스를 제네릭 인터페이스로 바꾸기
interface User<T> {
    name: string;
    profile: T;
};

// 학생 유저만 사용하는 함수
function goToSchool(user: User<Student>){ 
    if(user.profile.type !=='student'){
        console.log('잘 못 오셨습니다');
        return;
    };
    const school = user.profile.school;
    console.log(`${school}로 등교 완료`);
};

const developerUser: User<Developer> = { // 타입 변수의 타입 설정
    name: '기지',
    profile: {
       type: 'developer',
       skill: 'TypeScript', 
    },
};

const studentUser: User<Student> = { // 타입 변수의 타입 설정
    name: '죠니',
    profile: {
        type: 'student',
        school: '단국대학교',
    },
};

제네릭 함수를 이용해 타입 좁히기를 따로 하지 않아도 학생 유저만을 전달할 수 있어 간결한 코드를 작성할 수 있다.

 

 

 

 


🔗 타입스크립트 강의

 

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

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

www.inflearn.com

 

728x90