Adventure Time - Jake [TypeScript] 인터페이스와 타입 앨리어스의 개념 | 인터페이스(interface)와 타입 앨리어스(type alias)의 차이
본문 바로가기
Front-end/typescript

[TypeScript] 인터페이스와 타입 앨리어스의 개념 | 인터페이스(interface)와 타입 앨리어스(type alias)의 차이

by bogyoi 2024. 2. 4.

1. 인터페이스 (interface)

인터페이스(Interface)는 TypeScript에서 사용되는 중요한 개념으로, 객체의 구조를 정의하기 위한 것입니다. 주로 객체의 속성들의 타입과 메서드의 형태를 정의하는 데에 사용됩니다. 인터페이스를 사용함으로써 코드의 가독성을 높이고 유지보수를 쉽게 할 수 있습니다.

 

- 주로 객체의 구조를 정의할 때 사용됩니다.

- 객체의 속성 이름과 그에 상응하는 타입을 지정합니다.

- 여러가지 타입을 갖는 프로퍼티로 이루어진 새로운 타입을 정의하는 것과 유사합니다.

- 인터페이스는 확장(extends)이 가능합니다.

- 구조적인 타입 검사(structural typing)를 수행하며 변수, 함수, 클래스에 사용할 수 있습니다.

- 클래스에서 구현(implement)될 수 있습니다.

 

+) 프로퍼티와 메서드를 가질 수 있다는 점에서 클래스와 유사하나, 직접 인스턴스를 생성할 수 없고 모든 메서드는 추상 메서드입니다. 그러나 추상 클래스의 추상 메서드와 달리 abstract 키워드를 사용하지 않습니다. 추상 메서드는 구현되지 않고 선언만 되어 있어야 합니다. 인터페이스는 클래스나 객체를 생성하는 것이 아님. -> 인터페이스로 직접 객체 생성 불가 -> 인터페이스는 단순히 객체의 구조(타입)를 정의한다는 의미!(구현x)

 

// 인터페이스의 정의
interface Person {
  name: string; // 필수
  age?: number; // 선택
  PersonInfo() : void; //추상 메서드
}

// 변수 person의 타입으로 Person 인터페이스 선언
const person: Person = {
  name: "John", //인터페이스에서 정의한 프로퍼티 구현
  age: 30
  PersonInfo() {  // 인터페이스에서 정의한 추상 메서드를 구현
  	console.log(`${this.name}, ${this.age}`);
  }
};

person.PersonInfo();

인터페이스는 변수의 타입으로 사용할 수 있습니다. 인터페이스는 프로퍼티뿐만 아니라 메서드도 포함할 수 있습니다.

 

// 익명 인터페이스 적용
let ai: {
  name: string  // 필수입력
  age: number   // 필수입력
  etc?: boolean  // 선택입력
} = { name: "lee", age: 32 }


// 함수에 사용된 인터페이스 예
function printMe(me: { name: string, age: number, etc?: boolean }) {
  console.log(me.etc ?
    `${me.name} ${me.age} ${me.etc}` :
    `${me.name} ${me.age}`
  )

}
printMe(ai)  // lee 32

인터페이스는 함수 타입으로도 사용할 수 있으며,

'?'를 통해 필수로 구현하지 않아도 되는 프로퍼티를 정의할 수도 있습니다. (선택적 프로퍼티)

 

// 인터페이스의 정의 및 클래스
interface IPerson4 {
  name:string
  age?:number
}

class Person4 implements IPerson4 {
  constructor(public name:string, public age?:number) {} //인터페이스에서 정의한 프로퍼티 구현
}

let jack3 : IPerson4 = new Person4("jack3", 55)
console.log(jack3) // Person4 { name: 'jack3', age: 55 }

클래스 선언문의 implements 뒤에 인터페이스를 선언하여 일관성을 유지할 수도 있습니다.

인터페이스는 프로퍼티와 메소드를 가질 수 있다는점에서 클래스와 유사하나 직접 인스턴스를 생성할 수 는 없습니다.

 

// 인터페이스의 정의 및 함수에 사용
interface numSquer {
  // 숫자로 받아들여서, 문자로 출력해.
  (num:number) : string;  // 타입 선언 : 리턴 타입
}

const numFunc:numSquer = function(num:number) {
  return "입력값" + String(num)
}

console.log(numFunc(10)) // 입력값10

위의 numSquer 인터페이스는 숫자를 입력 받아 문자열을 반환하는 함수 타입을 나타냅니다.

numFunc 라는 변수에 numSquer 타입을 적용하여, 함수를 할당합니다. 이 함수는 numSquer 인터페이스 형태를 따르게 되며 숫자(number)를 입력받아 해당 숫자를 문자열(String)으로 반환합니다.

 

interface IDuck { 
  quack(): void;  // IDuck 인터페이스 안에 quack 메서드 정의
}

class MallardDuck implements IDuck { // interface IDuck을 구현
  quack() {
    console.log('Quack!');
  }
}

class RedheadDuck { // interface IDcuk 구현하지 않음 => quack() 메소드 정의
  quack() {
    console.log('q~uack!');
  }
}

function makeNoise(duck: IDuck): void { // interface IDuck을 사용
  duck.quack();
}

makeNoise(new MallardDuck()); // Quack!
makeNoise(new RedheadDuck()); // q~uack!

덕 타이핑은 객체의 실제 타입보다는, 객체가 갖고 있는 메서드나 프로퍼티의 존재에 따라 객체의 타입을 결정합니다. 
객체의 타입을 명시적으로 지정하지 않고도 객체의 메서드나 변수에 따라 객체를 해당 타입에 속하는 것으로 간주합니다. 즉, 동적 타이핑의 한 종류입니다. 

 

- 의문) 자바스크립트와 달리 타입스크립트는 정적 언어이고, 이를 위해 사용하는 타입스크립트에서 동적인 특성을 사용한다고? -> 글쎄.. 이 부분에 대해선 조금 더 공부를 해봐야겠다.


덕 타이핑은 객체의 동작 방식에 초점을 맞추어 유연하고 동적인 코드를 작성할 수 있도록 도와줍니다. 
객체의 실제 타입이 중요하지 않고 객체가 원하는 동작을 수행할 수 있다면, 해당 객체를 그에 맞는 타입으로 간주합니다.

 

 

interface Person {
  name: string; 
  age?: number; 
}

interface Student extends Person { // Person 을 상속받는 Student 객체
  grade: number;
}

const student: Student = {
  name: 'Lee',
  age: 21,
  grade: 2
}


interface Developer {
  skills: string[];
}

interface WebDeveloper extends Person, Developer {} // 복수의 인터페이스를 상속 받은 WebDeveloper 인터페이스 객체
const webDeveloper: WebDeveloper = {
  name: 'Kim',
  age: 20,
  skills: ['HTML', 'CSS', 'JS']
}

인터페이스는 'extends' 키워드를 통해 인터페이스 또는 클래스를 상속 받을 수 있고, 복수의 인터페이스를 상속 받을 수도 있습니다.

class Person {
  constructor(public name: string, public age: number) {}
}

interface Student extends Person { // Person클래스를 상속받는 Student 인터페이스 객체
  grade: number;
}

const student: Student = {
  name: 'Lee',
  age: 21,
  grade: 2
}

클래스의 모든 멤버(public, protected, private)가 상속되지만, 구현까지 상속되진 않습니다.

 

 

2. 타입 앨리어스 (Type Aliase)

타입 앨리어스(Type Aliases)는 기존에 존재하는 타입에 대한 별칭(alias)을 지정하는 TypeScript의 기능입니다. 이를 사용하여 기존 타입에 대해 짧고 명확한 이름을 정의하거나, 여러 번 반복해서 사용되는 복잡한 타입을 단순화할 수 있습니다.

 

- 주로 새로운 타입을 만들 때 사용됩니다.

- 기존에 정의된 타입들을 조합하거나 별칭을 부여할 때(복잡한 타입에 대해 명시적인 이름 제공) 유용합니다.

- 타입 앨리어스는 확장(extends)될 수 없습니다.

- 이름으로 구분되는 엄격한 타입 검사(named type)를 수행합니다.

- 인터페이스와 달리 유니온 타입, 인터섹션 타입 등을 더 유연하게 정의할 수 있습니다. (다양한 타입 정의 가능)

type Person = { // type 앨리어스를 이용한 정의
  name: string;
  age?: number;
};

const person1: Person = {
  name: "John",
  age: 30
};

// 빈 객체를 Parson 타입으로 지정
const person2 = {} as Person;

person2.name = "Lee";
person2.age = 33;
// person2.address="seoul";  //Error

타입 앨리어스는 위와 같이 type 키워드를 사용해 정의합니다.

 

// 문자열 리터럴로 타입 지정
type Str = 'Lee';

// 유니온 타입으로 타입 지정
type Union = string | null;

// 문자열 유니온 타입으로 타입 지정
type Name = 'Lee' | 'Kim';

// 숫자 리터럴 유니온 타입으로 타입 지정
type Num = 1 | 2 | 3 | 4 | 5;

// 객체 리터럴 유니온 타입으로 타입 지정
type Obj = { a: 1 } | { b: 2 };

// 함수 유니온 타입으로 타입 지정
type Func = (() => string) | (() => void);

// 인터페이스 유니온 타입으로 타입 지정
type Shape = Person | Rectangle;

// 튜플로 타입 지정
type Tuple = [string, boolean]
const tt: Tuple = ["aa", true];

타입 앨리어스는 원시값, 유니온 타입, 튜플 등도 타입으로 지정이 가능합니다.

 

type ID = string | number;  // 문자열 또는 숫자 타입을 가질 수 있는 유니온 타입

type Point = {  //x와 y 좌표를 가지는 객체
  x: number;
  y: number;
};

type Result<T> = {  //성공 여부와 함께 제네릭 타입 T를 가진 데이터를 포함하는 결과 객체
  success: boolean;
  data: T;
};

이렇게 타입 앨리어스로 정의된 'ID', 'Point', 'Result' 은 각각의 이름을 사용해 해당 타입을 참조할 수 있습니다. :)

 

 

 

 

3. 결론

// 인터페이스 선언 및 확장
interface Person {
  name: string;
}

interface Person {
  age: number;
}

// 타입 앨리어스 선언
type Animal = {
  name: string;
};

// 아래와 같이 같은 이름으로 선언한 타입 앨리어스를 확장하려고 하면 오류가 발생함
/*
type Animal = { age: number; };
*/

// 인터페이스를 사용한 객체 생성
const person: Person = { name: "John", age: 30,};

// 타입 앨리어스를 사용한 객체 생성
const animal: Animal = { name: "Dog", };

 

타입 앨리어스는 extends 또는 implements 될 수 없습니다.

새로운 속성을 추가하기위해 같은 이름으로 선언이 불가하다는 것입니다.

따라서, 상속을 통해 확장이 필요하다면 인터페이스가 유리합니다.

 

인터페이스로 표현할 수 없거나, 유니온 또는 튜플을 사용해야한다면 타입 앨리어스를 사용하는 편이 유리합니다.

일반적으로 객체의 구조를 정의하거나 클래스와 함께 사용할 때에는 인터페이스를 사용하고, 여러 타입을 조합하거나 복잡한 타입을 단순화할 때 타입 앨리어스를 사용합니다. 

 

대부분의 상황에서는 인터페이스를 사용하며, 인터페이스를 사용하는 편을 조금 더 권장한다는 의견이 많습니다.