📍
여러 타입스크립트 강의를 들으면서
'타입과 인터페이스는 비슷한 기능을 가졌다. 그러나 무엇을 쓰는걸 더 권장한다.'
라는 말을 많이 들었는데 중요한건 그 '무엇을'이 다 다르다는 것이다.
궁금함에 멘토링 시간에 멘토님께도 물어봤는데 또 다른 걸 권장하신다는 걸 듣고
과연 타입과 인터페이스 중 무엇을 써야될까에 대한 고민이 깊어졌다.
1. 타입 별칭(Type Alias)
// 타입 별칭
type User = {
id: number;
name: string;
nickname: string;
birth: string;
bio: string;
location: string;
};
...
- 타입 별칭을 이용하면 변수를 선언하듯 타입을 별도로 정의할 수 있다.
- type 타입이름 = { 타입 }
- 타입에는 여러개의 프로퍼티가 있는 객체 타입으로 정의할 수 있다.
type User = {
id: number;
name: string;
nickname: string;
birth: string;
bio: string;
location: string;
};
let user1: User = {
id: 1,
name: "홍길동",
nickname: "winterlood",
birth: "1997.01.07",
bio: "안녕하세요",
location: "부천시",
};
- 타입별칭은 변수의 타입을 정의할 때 타입 주석과 함께 이용할 수 있다.
type User = {
id: number;
name: string;
nickname: string;
birth: string;
bio: string;
location: string;
};
type User = {} // ❌ NO
function test() { // ✅ OK
type User = string;
}
- 단, 동일한 스코프에 동일한 이름의 타입 별칭을 선언하는 것은 불가능하다. → 변수 선언과 비슷
- 스코프가 다르다면 중복된 이름으로 여러개의 별칭을 선언 가능하다.
2. 인터페이스(Interface)
interface Person {
name: string;
age: number;
}
const person: Person = {
name: "홍길동",
age : 27
};
- 인터페이스란 타입 별칭과 동일하게 타입에 이름을 지어주는 또 다른 문법이다.
- 예제와 같이 정의된 인터페이스를 타입 주석과 함께 사용해 변수의 타입을 정의할 수 있다.
- 즉, 인터페이스는 타입 별칭과 문법만 조금 다를 뿐 기본적으로 비슷한 기능을 가지고 있다.
2.1 선택적 프로퍼티
interface Person {
name: string;
age?: number;
}
const person: Person = {
name: "홍길동",
// age: 27,
};
2.2 읽기 전용 프로퍼티
interface Person {
readonly name: string;
age?: number;
}
const person: Person = {
name: "홍길동",
// age: 27,
};
person.name = '이몽룡' // ❌
2.3 메서드 타입 정의하기
interface Person {
readonly name: string;
age?: number;
sayHi: () => void; // 함수 타입 표현식
sayHi(): void; // 호출 시그니처
}
- 함수 타입 표현식을 이용해 메서드의 타입을 정의할 수 있다.
- 함수 타입 표현식이 아닌 호출 시그니처를 이용해 메서드의 타입을 정의할 수도 있다.
2.4 메서드 오버로딩
interface Person {
readonly name: string;
age?: number;
sayHi: () => void;
sayHi: (a: number, b: number) => void; // ❌
sayHi(): void; // ✅
sayHi(a: number): void; // ✅
sayHi(a: number, b: number): void; // ✅
}
- 함수 타입 표현식으로 메서드의 타입을 정의하면 메서드의 오버로딩 구현이 불가능하다.
- 호출 시그니처를 이용해 메서드의 타입을 정의하면 오버로딩 구현이 가능하다.
2.5 하이브리드 타입
interface Func2 {
(a: number): string;
b: boolean;
}
const func: Func2 = (a) => "hello";
func.b = true;
- 함수이자 일반 객체인 하이브리드 타입을 정의할 수 있다.
3. 인터페이스 확장
interface Animal {
name: string;
age: number;
}
interface Dog {
name: string;
age: number;
isBark: boolean;
}
interface Cat {
name: string;
age: number;
isScratch: boolean;
}
interface Chicken {
name: string;
age: number;
isFly: boolean;
}
- 인터페이스 확장이란 하나의 인터페이스를 다른 인터페이스들이 상속받아 중복된 프로퍼티를 정의하지 않도록 도와주는 문법이다.
- 예제에서 보면 각 타입들은 Animal 타입을 기반으로 각각 중복되는 두개의 프로퍼티와 추가적인 프로퍼티를 하나씩 가지고 있다. 만약 기반이 되는 타입이 수정된다면 중복되는 모든 타입들을 수정해줘야하는 번거로움이 생긴다.
- 특정 인터페이스를 기반으로 여러 개의 인터페이스가 파생되는 경우 중복코드가 발생할 수 있는데 이때 인터페이스의 확장 기능을 사용하면 된다.
interface Animal {
name: string;
color: string;
}
interface Dog extends Animal {
breed: string;
}
interface Cat extends Animal {
isScratch: boolean;
}
interface Chicken extends Animal {
isFly: boolean;
}
const dog: Dog = {
name: "돌돌이",
color: "brown",
breed: "진도",
};
- interface 타입이름 extends 확장할_타입이름
- extends 뒤에 확장할 타입의 이름을 정의하면 해당 타입에 정의된 모든 프로퍼티를 다 가지고 오게된다.
- 확장 대상 타입인 Animal 타입은 Dog 타입의 슈퍼 타입이 된다.
3.1 프로퍼티 재 정의하기
interface Animal {
name: string;
color: string;
}
interface Dog extends Animal {
name: "doldol"; // 타입 재 정의
breed: string;
}
- Dog 타입은 Animal 타입을 확장하며 동시에 name 프로퍼티 타입을 string 타입에서 "doldol" 문자열 리터럴 타입으로 재정의 했다. 이렇게 확장받는 타입에서 프로퍼티의 타입을 재정의 할 수 있다.
interface Animal {
name: string;
color: string;
}
interface Dog extends Animal {
name: number; // ❌
breed: string;
}
- 단, 프로퍼티를 재 정의할 때 원본 타입이 재 정의된 타입의 슈퍼 타입이 되도록 재정의 해야한다.
- 즉, 재 정의한 타입이 반드시 서브 타입이 되도록 해야 재 정의할 수 있다는 것이다.
3.2 타입 별칭 확장하기
type Animal = {
name: string;
color: string;
};
interface Dog extends Animal {
breed: string;
}
- 인터페이스는 타입 별칭으로 정의된 객체 또한 확장할 수 있다.
3.3 다중 확장
interface DogCat extends Dog, Cat {}
const dogCat: DogCat = {
name: "",
color: "",
breed: "",
isScratch: true,
};
- 여러 개의 인터페이스를 확장하는 것 또한 가능하다.
4. 인터페이스 선언 합침
type Person = {
name: string;
};
type Person = { ❌
age: number;
};
interface Person {
name: string;
}
interface Person { ✅
age: number;
}
// 선언 합침
const person: Person = {
name: "홍길동",
age: 27,
};
- 타입 별칭은 동일한 스코프 내에 중복된 이름으로 선언할 수 없는 반면 인터페이스는 가능하다.
- 중복된 이름의 인터페이스 선언은 결국 하나로 합쳐지기 때문에 가능하다. 이처럼 동일한 이름의 인터페이스 들이 합쳐지는 것을 선언 합침(Declaration Merging) 이라고 한다.
interface Person {
name: string;
}
interface Person {
name: number; // 오류 발생
age: number;
}
- 단, 동일한 이름의 인터페이스들이 동일한 이름의 프로퍼티를 서로 다른 타입으로 정의하면 오류가 발생한다. → 충돌발생, 선언 합침에서는 허용하지 않음
5. 타입 별칭 vs 인터페이스
타입 별칭 | 인터페이스 |
Union 타입 ⭕ | Union 타입 ❌ |
Intersection 타입 ⭕ | Intersection 타입 ❌ |
tuple 타입 ⭕ | tuple 타입 ❌ |
Intersection으로 확장 | extends로 확장 |
선언 합침 ❌ | 선언 합침 ⭕ |
type Type1 = number | string;
type Type2 = number & string;
interface Person {
name: string;
age: number;
} | number // ❌
type Type1 = number | string | Person;
type Type2 = number & string & Person;
const person: Person & string = {
name: "홍길동",
age: 27,
};
- 인터페이스로 만든 타입을 Union 또는 Intersection으로 이용해야 한다면 타입 별칭과 함께 사용하거나 타입 주석에서 직접 사용해야한다.
💡 결론적으로 어떤 걸 쓰는 것이 좋은가?
type단
👨🏼💻 타입스크립트 이지 인터페이스크립트가 아니지 않은가?
👩🏼💻 인터페이스는 객체 형태만 타입 지정이 가능하다.
👨🏼💻 인터페이스의 확장은 타입에서 더 간단하게 사용할 수 있다.
👩🏼💻 인터페이스는 중복된 이름 선언이 가능해서 복잡해질 수 있다.
interface단
👩🏼💻 대부분의 경우 객체를 정의하는 일이많다.
👨🏼💻 타입의 복잡한 기능들이 실제로 필요한가 의문이 든다.
👨🏼💻 인터페이스가 더 엄격하게 느껴지고 extends 확장 키워드가 더 깔끔하게 느껴진다.
중립단
👨🏼💻 객체에는 인터페이스를 다른곳에는 타입을.. 그냥둘다써라.
결론
이 영상을 보면 두 문법에 대한 다양한 의견이 존재한다. 딱 뭐를 써라 결론이 나지는 않지만 이런 논쟁(?)이 있는게 흥미롭다. 타입스크립트를 사용하는게 익숙해지면 나도 자연스럽게 더 선호하는게 생기지 않을까 싶다.
Why use Type and not Interface in TypeScript
📌
한 입 크기로 잘라먹는 타입스크립트(TypeScript)
'Front-end > TypeScript' 카테고리의 다른 글
[TS] 함수와 타입 (0) | 2024.02.20 |
---|---|
[TS] 타입을 추론하고... 단언하고... 좁히고... (0) | 2024.02.19 |
[TS] 타입은 집합이다? (1) | 2024.02.17 |
[TS] 타입스크립트의 기본 타입 (0) | 2024.02.16 |
[TS] 타입스크립트 컴파일러 옵션 알아보기 (0) | 2024.02.15 |