글 작성자: 망고좋아
반응형

🎯 인터페이스와 타입 별칭

  • 타입스크립트에서 타입을 기술하는 2가지 방법이 있다. ⇒ 인터페이스, 타입 별칭(타입 알리아스)
  • 타입 별칭은 특정 타입이나 인터페이스를 참조할 수 있는 타입 변수를 의미한다.
  • 인터페이스는 상호 간에 정의한 약속 혹은 규칙을 의미한다.

 

📝 타입 별칭 (Type Aliases)

// string 타입을 사용할 때
const name: string = 'capt';

// 타입 별칭을 사용할 때
type MyName = string;
const name: MyName = 'capt';

// Ex
type Name = string;
type Email= string;
type FooFunction = () => string; // 함수도 타입을 가지고 규정할 수 있다. 반환값이 string
  • 타입 별칭은 특정 타입이나 인터페이스를 참조할 수 있는 타입 변수를 의미한다.
  • 변수명에 의미를 부여할 수 있지만 string 타입에 의미를 부여하고 싶을 때 타입 별명(타입 별칭, 타입 알리아스)을 사용한다.
  • 타입 별칭은 새로운 타입 값을 하나 생성하는 것이 아니라 정의한 타입에 대해 나중에 쉽게 참고할 수 있게 이름을 부여하는 것과 같다.
  • 타입 별칭은 첫 글자를 대문자로 쓰는 컨벤션을 따름

 

type Developer = {
  name: string;
  skill: string;
}
  • interface 레벨의 복잡한 타입에도 별칭을 부여할 수 있다.

 

type User<T> = {
  name: T
}
  • 타입 별칭에 제네릭도 사용할 수 있다.

 

type YesOrNo = 'Y' | 'N';
type DayOfWeek = '월' | '화' | '수' | '목' | '금' | '토' | '일';
  • 위 두 개는 문자열로만 제한하는 유니언 타입을 가지고 있다.
  • 타입으로 기술된 것은 컴파일 타임에 이 값이 들어가는지 안 들어가는지, 그런 코드가 있는 확인하는 용도로 사용된다.

 

enum DayOfTheWeek = {'월', '화', '수', '목', '금', '토', '일'};
  • 반면 enum은 실제 데이터다. 컴파일 타임이 아닌 런타임에 실제로 월화수목금..이 들어간다.
  • enum은 특정 값으로 제한하는 기능은 유사하지만 실제 데이터고, 타입 알리아스는 컴파일 타임에 타입만 검사하는 용도로 사용된다는 차이점이 있다.

 

📕 여러 개의 타입 별칭을 하나로 합치기 :: intersection

type TUserProfile = IUser & {
    profileImage: string;
    github?: string;
    twitter?: string;    
}
  • IUser를 상속받아서 intersection(&) 교차시킬 거다! merge 되는 것과 똑같은 효과를 가진다.
  • IUser 자리에 타입 알리아스가 오든 interface가 오든 상관하지 않는다.

 

📝 인터페이스(interface)

interface IUser {
    readonly id: number;
    readonly name: Name;
    email: string;
    receiveInfo: boolean;
    active: YesOrNo;
}
  • 인터페이스는 상호 간에 정의한 약속 혹은 규칙을 의미한다.
  • 인터페이스와 타입 알리아스를 섞어 사용할 수 있다.
  • IUser라는 객체 데이터를 만들면 저런 규격으로 만들어야 해!라는 것을 의미한다.

 

// logAge() 함수에서 받는 인자의 형태는 age를 속성으로 갖는 객체
let person = { name: 'Capt', age: 28 };

function logAge(obj: { age: number }) {
  console.log(obj.age); // 28
}
logAge(person); // 28



// 인터페이스로 변환
interface personAge {
  age: number;
}

function logAge(obj: personAge) {
  console.log(obj.age);
}
let person = { name: 'Capt', age: 28 };
logAge(person);
  • logAge()의 인자는 personAge 라는 타입을 가져야 한다.
  • 인터페이스를 인자로 받아 사용할 때 항상 인터페이스의 속성 개수와 인자로 받는 객체의 속성 개수를 일치시키지 않아도 된다.
  • 즉, 인터페이스에 정의된 속성, 타입의 조건만 만족한다면 객체의 속성 개수가 더 많아도 상관없다.
  • 또한, 인터페이스에 선언된 속성 순서를 지키지 않아도 된다.

 

🛠 예시 코드

import * as allTypes from './type';

const iUser: allTypes.IUser = {
    id:1,
    name: '빌 게이츠',
    email: 'bill@ms.com',
    receiveInfo: false;
    active: 'Y';
}

const foo: allTypes.FooFunction = function() {
    return 'test';
}
  • 객체 사용하듯이 사용한다.
  • import *을 사용하면 뒤에 별칭을 붙여줘야 된다.
  • 가지고 올 요소들이 많으면 개별로 매번 import 해주는 것보다 *를 사용하는 게 편리할 수 있다.

 

📕 옵셔녈(?)과 읽기 전용 속성(readonly)

export interface IUser {
    readonly id: number;
    readonly name: Name;
    email: string;
    receiveInfo: boolean;
    active: YesOrNo;
}

export interface IUser {
    address?: string;
}

// 인터페이스는 이름이 중복되면 아래처럼 이러한 효과를 낸다.
// 하나인데 2개로 나뉘어 놨구나~ 라고 인지한다.
// 하지만 이렇게 나눠서 사용하는 것은 비추천한다. -> 알아보기 힘들다.
// => 하나에 몰아서 넣자!
export interface IUser {
    readonly id: number;
    readonly name: Name;
    email: string;
    receiveInfo: boolean;
    active: YesOrNo;
    address?: string;
}
  • ? 는 옵셔널을 뜻한다.
  • 객체를 만드는데 객체의 address의 속성은 있어도 되고 없어도 된다.
  • 읽기 전용 속성은 인터페이스로 객체를 처음 생성할 때만 값을 할당하고 그 이후에는 변경할 수 없는 속성을 의미한다. readonly 키워드를 속성을 앞에 붙인다.

 

📕 여러 개의 인터페이스를 하나로 합치기

export interface IUserProfile extends IUser {
    profileImage: string;
    github?: string;
    twitter?: string;    
}
  • 상속과 유사한 느낌이다.
  • IUser속성이 반복되어서 사용된다면 클래스의 상속과 유사하게 extends 키워드를 사용해서 인터페이스를 확장할 수 있다. → IUser를 재활용할 수 있는 문법 요소이다.
  • extends 뒤에 interface가 아닌 타입 알리아스가 와도 상관없다.
export interface Color {
    fontColor: string;
    strokeColor: string;
    borderColor: string;
    backgroundColor: string;
}

export type Display = {
    display: 'none' | 'block';
    visiblity: boolean;
    opacity: number;
}

export type Geometry = {
    width: number;
    height: number;
    padding: number;
    margin: number;
}

//-----------------------------------

export interface IStyle extends Color, Display, Geometry {
    tagName: string;
}

export type TStyle = Color & Display & Geometry & {
    tagName: string;
}

 

📕 함수에 사용하기

// 함수도 가능
interface IGetApi {
    (url: string. search?: string): Promise<string>; // : 뒤는 함수의 return값, ()안은 url과 search인자를 받는 함수
}
  • 함수의 규격 정의할 때는 화살표 함수를 못 쓴다.
  • 이렇게 함수 규격을 함수에 적용할 때는 함수 정의문이 아닌 함수 표현식을 사용해야 된다.

 

const getApi: allTypes.IGetApi = (url, search = '') => {
    return new Pomise(resolve => recolve('OK'));
};
  • 타입을 지정하기 위해서는 함수 표현식을 사용해야 된다.

 

코드

interface login {
  (username: string, password: string): boolean;
}


let loginUser: login;
loginUser = function(id: string, pw: string) {
  console.log('로그인 했습니다');
  return true;
}

 

📕 클래스에 사용하기

export interface IRect {
    id: number;
    x: number;
    y: number;
    width: number;
    height: number;
}

export interface IRectConstructor {
    // 생성자로 호출할 때 생성자의 스펙은 이렇게 돼! 라고 인터페이스로 묘사
    new (x: number, y: number, width: number, height: number) : IRect; // 반환값으로 IRect 즉, 이 클레스의 인터페이스라고 하는 형식 
}

class Rect implements allTypes.IRect {
    private id: number; // * private로 바꿔주는건 불가능하다. only public
    x: number;
    y: number;
    width: number;
    height: number;

    constructor(x: number, y: number, width: number, height: number) {
        this.id = Math.random() * 100000;
        this.x = x; 
        this.y = y;
        this.width = width; 
        this.height = height;
    }

}

function createDefaultRect(cstor: allTypes.IRect) {
    return new cstor(0, 0, 100, 20); //클래스의 생성자를 호출할 때 생성자 인터페이스 필요
}


const rect1 = new Rect(0, 0, 100, 20);
const rect2 = createDefaultRect(Rect);
  • interface는 항상 public, 공개된 속성만 기술한다.
  • 클래스 같은 경우 인스턴스를 만들기 위해서 생성자가 호출된다. → 클래스의 규격과 생성자가 만들어내는 인스턴스의 규격이 미묘하게 다를 수 있다.
  • 그래서 인터페이스를 이용해서 생성자의 규격을 기술할 수 있다.

 

📝 type vs interface

export interface IUser {
    readonly id: number;
  readonly name: Name;
    email: string;
    receiveInfo: boolean;
    active: YesOrNo;
}

export type TUser =  {
    readonly id: number;
  readonly name: Name;
    email: string;
    receiveInfo: boolean;
    active: YesOrNo;
}
  • 타입 별칭과 인터페이스의 가장 큰 차이점은 타입의 확장 가능 / 불가능 여부이다.
  • 인터페이스는 확장이 가능한데 반해 타입 별칭은 확장이 불가능하다.
  • 데이터를 표현할 때는 타입 알리아스 사용
  • 데이터를 포괄하는 객체를 묘사하는 경우에는 인터페이스를 사용(메소드와 같이 구체적인 행위까지 포함된 객체를 디자인할 때)
  • 클래스를 묘사할 때는 클래스 자체가 데이터와 행위를 포괄하고 있기 때문에 인터페이스를 사용하는 것이 훨씬 자연스럽다.

 

📌 참고

반응형