💡인터페이스
• 인터페이스란 ?
인터페이스란 타입 별칭과 동일하게 타입에 이름을 지어주는 문법입니다.
interface Person {
name: string;
age: number;
}
const person: Person = {
name: "수현",
age : 27
};
이와 같이 Person 객체의 타입을 정의한다면 이렇게 정의한 인터페이스를 타입 주석과 함께 사용하여 변수의 타입을 정의할 수 있습니다. 이렇게 인터페이스는 타입 별칭과 문법만 조금 다를 뿐 기본적인 기능은 거의 같습니다.
• 선택적 프로퍼티
interface Person {
name: string;
age?: number;
}
const person: Person = {
name: "수현",
// age : 27
};
인터페이스에서도 변수명 뒤에 ?를 붙임으로써 선택적 프로퍼티 설정이 가능합니다.
• 읽기 전용 프로퍼티
interface Person {
readonly name: string;
age?: number;
}
const person: Person = {
name: "수현",
// age : 27
};
person.name = "shyunu"; // ❌
인터페이스에서 읽기 전용 프로퍼티를 설정하고자 한다면 변수명 앞에 readonly 를 붙여줌으로써 설정 가능합니다. 현재 name은 읽기 전용 프로퍼티가 되었으므로 person.name으로 접근하여 값을 변경하고자 한 코드에서는 오류가 발생합니다.
• 메서드 타입 정의하기
interface Person {
readonly name: string;
age?: number;
sayHi: () => void; // ⓵번 방법
}
interface Person {
readonly name: string;
age?: number;
sayHi(): void; // ⓶번 방법
}
함수 타입 표현식을 이용하여 sayHi 메서드의 타입을 정의하였습니다. 함수 타입 표현식 말고 다음과 같이 호출 시그니쳐를 이용하여 메서드의 타입을 정의할 수도 있습니다.
• 메서드 오버로딩
interface Person {
readonly name: string;
age?: number;
sayHi: () => void;
sayHi: (a: number, b: number) => void; // ❌
}
interface Person {
readonly name: string;
age?: number;
sayHi(): void;
sayHi(a: number, b: number): void; // ⭕️
}
함수 타입 표현식으로 메서드의 타입을 정의하면 메서드의 오버로딩이 불가능합니다. 하지만 호출 시그니쳐를 이용하여 메서드의 타입을 정의하면 오버로딩 구현이 가능합니다.
• 하이브리드 타입
interface Func2 {
(a: number): string;
b: boolean;
}
const func: Func2 = (a) => "hello";
func.b = true;
인터페이스 또한 함수이자 일반 객체인 하이브리드 타입을 정의할 수 있습니다.
• Union / Intersection 타입
type Type1 = number | string | Person;
type Type2 = number & string & Person;
const person: Person & string = {
name: "수현",
age: 27,
};
인터페이스로 만든 타입을 Union 또는 Intersection으로 이용해야한다면 타입 별칭과 함꼐 사용하거나 타입 주석에서 직접 사용해야합니다.
💡 인터페이스 확장
인터페이스 확장이란 하나의 인터페이스를 다른 인터페이스들이 상속받아 중복된 프로퍼티를 정의하지 않도록 도와주는 문법입니다.
• 인터페이스의 기본 사용법
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;
}
각 타입들을 자세히 살펴보면 Dog, Cat, Chicken 세가지 모두 Animal 타입을 기반으로 각각 추가적인 프로퍼티를 갖고 있는 형태입니다. 그리고 name과 age 프로퍼티가 모든 타입에 중복해서 정의되어있음도 확인할 수 있습니다.
하지만 중복코드는 좋지 않습니다. 만약 Animal 타입의 프로퍼티가 변경된다면 이를 기반으로 하는 나머지 타입들도 모두 수정되어야하기 때문입니다. 이렇게 특정 인터페이스를 기반으로 여러개의 인터페이스가 파생되는 경우 중복 코드가 발생할 수 있는데 이러한 경우에는 인터페이스의 확장 기능을 이용하면 좋습니다.
interface Animal {
name: string;
color: string;
}
interface Dog extends Animal {
isBark: boolean;
}
interface Cat extends Animal {
isScratch: boolean;
}
interface Chicken extends Animal {
isFly: boolean;
}
interface 타입이름 extends 확장할 타입이름 형태로 extends 뒤에 확장할 타입의 이름ㅇ르 정의하면 해당 타입에 정의된 모든 프로퍼티를 모두 가지고 올 수 있습니다. 따라서 Dog, Cat, Chicken 타입은 모두 Animal 타입을 확장하는 타입이기 때문에 name과 age 프로퍼티를 가지게 됩니다. 이때, 확장 대상 타입인 Animal은 Dog 타입의 슈퍼타입이 됩니다.
• 프로퍼티 재정의하기
interface Animal {
name: string;
color: string;
}
interface Dog extends Animal {
name: "별이"; // 타입 재 정의
breed: string;
}
다음과 같이 확장과 동시에 프로퍼티의 타입을 재정의하는 것 또한 가능합니다. Dog 타입은 Animal 타입을 확장하며 동시에 name 프로퍼티의 타입을 String 타입에서 "별이"라는 String Literal 타입을 재정의하였습니다. 이렇게 확장받는 타입에서 프로퍼티의 타입을 재정의할 수 있습니다. 하지만 조건이 있습니다. 프로퍼티를 재정의할 때, 원본타입을 A 그리고 재정의된 타입을 B라고 하면 반드시 A가 B의 슈퍼타입이 되도록 재정의해야합니다.
interface Animal {
name: string;
color: string;
}
interface Dog extends Animal {
name: number; // ❌
breed: string;
}
따라서 다음과 같이 string 타입이었던 name 프로퍼티의 타입을 number로 재정의하는 것은 불가능합니다. 확장하는 개념 자체는 Dog 타입이 Animal 타입의 서브타입이 된다는 의미입니다. 하지만 name 프로퍼티를 number 타입으로 재정의해버리면 이제는 Dog는 Animal의 서브타입이 아니게 되어버립니다.
• 타입 별칭을 확장하기
type Animal = {
name: string;
color: string;
};
interface Dog extends Animal {
breed: string;
}
인터페이스는 인터페이스 뿐만 아니라 타입 별칭으로 정의된 객체도 확장할 수 있습니다.
• 다중 확장
interface DogCat extends Dog, Cat {}
const dogCat: DogCat = {
name: "",
color: "",
isBark: true,
isScratch: true,
};
또한 여러개의 인터페이스를 확장하는 것 또한 가능합니다.
💡 인터페이스 선언 합치기
• 선언 합침
type Person = {
name: string;
};
type Person = { // ❌
age: number;
};
interface Person {
name: string;
}
interface Person { // ⭕️
age: number;
}
타입 별칭은 동일한 스코프 내에서 중복된 이름으로 선언할 수 없지만, 인터페이스는 가능합니다. 왜냐하면 중복된 이름의 인터페이스 선언은 결국 모두 하나로 합쳐지기 떄문입니다.
interface Person {
name: string;
age: number;
}
따라서 위에서 따로 선언한 Person 인터페이스들은 이와 같이 합쳐져 이러한 인터페이스가 됩니다. 이렇게 동일한 이름의 인터페이스들이 합쳐지는 것을 선언 합침(Declaration Merging)이라고 부릅니다.
interface Person {
name: string;
}
interface Person {
age: number;
}
const person: Person = {
name: "수현",
age: 27,
};
다음과 같이 사용해도 괜찮습니다.
interface Person {
name: string;
}
interface Person {
name: number; // ❌ 타입 충돌
name: "hello"; // ❌ 타입합치기의 경우에는 서브타입도 안되고 완전히 동일해야 선언 가능하다.
age: number;
}
그런데 다음과 같이 동일한 이름의 인터페이스들이 동일한 이름의 프로퍼티를 서로 다른 타입으로 정의하려고 하면 오류가 발생합니다. 첫번째 Person에서는 name 프로퍼티의 타입을 string으로, 두번쨰 Person에서는 name 프로퍼티의 타입을 number 타입으로 정의하였습니다. 이렇게 동일한 프로퍼티의 타입을 다르게 정의한 상황을 타입 충돌이라고 하며, 선언 합침에서는 이러한 충돌은 허용되지 않습니다.
출처) 한 입 크기로 잘라먹는 타입스크립트(TypeScript)_이정환
'📍 프로그래밍 언어 > TypeScript' 카테고리의 다른 글
[ TypeScript ] 제네릭 이해하기: 타입 변수와 메서드 타입 정의(map, forEach) (1) | 2024.12.22 |
---|---|
[ TypeScript ] 클래스 이해하기: JavaScript 차이점, 인터페이스, 그리고 접근 제어자 (0) | 2024.12.22 |
[ TypeScript ] 함수 오버로딩과 커스텀 타입가드로 복잡한 타입 관리하기 (0) | 2024.12.19 |
[ TypeScript ] 함수 타입: 정의와 표현식, 호출 시그니처, 타입 호환성 (1) | 2024.12.19 |
[ TypeScript ] 타입 단언과 타입 좁히기 활용법, 서로소 유니온 타입의 이해 (0) | 2024.12.19 |