1. 인터페이스란?
인터페이스는 객체의 타입을 정의할 때 사용되는 타입스크립트의 기능입니다. 인터페이스는 타입 별칭(type)과 비슷하게 특정 타입 구조를 정의하지만, 객체의 형태를 좀 더 명확하게 표현할 수 있다는 특징이 있습니다. 다음은 간단한 예시입니다:
interface Person {
name: string;
age: number;
}
위와 같이 정의한 Person 인터페이스는 객체의 타입을 정의하는 데 사용됩니다:
const person: Person = {
name: "이정환",
age: 27
};
인터페이스는 타입 별칭과 기능이 유사하지만, 주로 객체의 구조를 명확하게 기술할 때 많이 사용됩니다.
2. 선택적 프로퍼티(Optional Property)
인터페이스의 프로퍼티는 선택적으로 설정할 수도 있습니다. 선택적 프로퍼티는 ?를 사용하여 정의합니다:
interface Person {
name: string;
age?: number; // 선택적 프로퍼티
}
다음과 같이 age를 생략할 수 있습니다:
const person: Person = {
name: "이정환"
};
3. 읽기 전용 프로퍼티(Readonly Property)
읽기 전용 프로퍼티는 객체가 생성된 이후에는 변경할 수 없도록 합니다. readonly 키워드를 사용하여 정의합니다:
interface Person {
readonly name: string;
age?: number;
}
이렇게 정의된 name 프로퍼티는 객체 생성 이후 변경이 불가능합니다:
const person: Person = {
name: "이정환"
};
person.name = "홍길동"; // ❌ 오류 발생
4. 메서드 타입 정의하기
인터페이스 내에서 메서드의 타입을 정의할 수도 있습니다. 이는 함수 타입 표현식을 사용하거나 호출 시그니처를 사용하는 방식으로 정의합니다:
- 함수 타입 표현식 사용:
- interface Person { sayHi: () => void; }
- 호출 시그니처 사용:
- interface Person { sayHi(): void; }
5. 메서드 오버로딩(Method Overloading)
함수 타입 표현식을 사용할 경우 메서드 오버로딩은 불가능합니다. 하지만 호출 시그니처를 사용하면 메서드 오버로딩을 구현할 수 있습니다:
interface Person {
sayHi(): void;
sayHi(a: number): void;
sayHi(a: number, b: number): void;
}
위처럼 여러 시그니처를 정의함으로써 다양한 형태로 메서드를 호출할 수 있습니다.
6. 하이브리드 타입 (Hybrid Type)
인터페이스를 이용해 함수와 객체의 형태를 동시에 가지는 하이브리드 타입을 정의할 수도 있습니다:
interface Func2 {
(a: number): string; // 함수 타입 정의
b: boolean; // 프로퍼티 정의
}
const func: Func2 = (a) => "hello";
func.b = true;
이렇게 하면 함수인 동시에 객체 형태의 프로퍼티도 가지는 타입을 정의할 수 있습니다.
7. 타입 별칭과의 차이점
인터페이스와 타입 별칭은 대부분의 상황에서 비슷하게 사용될 수 있지만 몇 가지 차이점이 있습니다:
- Union과 Intersection 타입:
- 타입 별칭에서는 Union(|)이나 Intersection(&) 타입을 정의할 수 있습니다. 예를 들어, 다음과 같이 사용할 수 있습니다:
- type Type1 = number | string; // 가능 type Type2 = number & string; // 가능
- 그러나 인터페이스에서는 이러한 Union이나 Intersection을 직접 정의할 수 없습니다:
- interface Person { name: string; age: number; } | number; // ❌ 불가능
- 따라서 인터페이스로 만든 타입을 Union 또는 Intersection으로 이용해야 한다면, 타입 별칭을 사용하거나 타입 주석에서 직접 사용하는 것이 필요합니다:
- type Type1 = number | string | Person; type Type2 = number & string & Person; const person: Person & string = { name: "이정환", age: 27, };
- 확장 가능성:
- 인터페이스는 확장이 용이합니다. extends 키워드를 통해 다른 인터페이스를 상속받아 재사용할 수 있습니다:
- interface Person { name: string; age: number; } interface Employee extends Person { salary: number; }
- 반면, 타입 별칭은 확장할 때 & 연산자를 사용해야 하며, 인터페이스의 확장성에 비해 제한적일 수 있습니다.
- type Person = { name: string; age: number; }; type Employee = Person & { salary: number; };
- 선언 병합(Declaration Merging):
- 인터페이스는 선언 병합이 가능합니다. 즉, 동일한 이름의 인터페이스를 여러 번 선언하면, 타입스크립트가 이를 병합하여 하나의 타입으로 만듭니다:
- interface Person { name: string; } interface Person { age: number; } // 병합된 형태: const person: Person = { name: "이정환", age: 27 };
- 타입 별칭은 이러한 병합이 불가능하며, 동일한 이름으로 선언하면 오류가 발생합니다.
8. 인터페이스 확장(Interface Extension)
인터페이스 확장이란 하나의 인터페이스를 다른 인터페이스들이 상속받아 중복된 프로퍼티를 정의하지 않도록 도와주는 기능입니다. 다음 예시를 보겠습니다:
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
isBark: boolean;
}
interface Cat extends Animal {
isScratch: boolean;
}
interface Chicken extends Animal {
isFly: boolean;
}
여기서 Dog, Cat, Chicken 인터페이스는 모두 Animal 인터페이스를 확장하고 있습니다. 따라서 name과 age 프로퍼티를 중복해서 정의하지 않아도 됩니다. 이처럼 인터페이스 확장은 중복 코드를 줄이고, 유지보수를 더 쉽게 해줍니다.
만약 Animal의 프로퍼티를 수정해야 할 경우, 확장된 모든 타입에서 자동으로 해당 변경 사항이 반영되기 때문에 더 효율적입니다:
interface Animal {
name: string;
color: string;
}
interface Dog extends Animal {
breed: string;
}
const dog: Dog = {
name: "돌돌이",
color: "brown",
breed: "진도",
};
이 예시에서 Dog는 Animal의 프로퍼티를 모두 상속받고, 추가적인 프로퍼티인 breed만 정의하면 됩니다.
9. 프로퍼티 재정의하기
인터페이스를 확장하면서 기존의 프로퍼티를 재정의하는 것도 가능합니다. 예를 들어:
interface Animal {
name: string;
color: string;
}
interface Dog extends Animal {
name: "doldol"; // 타입 재정의
breed: string;
}
여기서 Dog 인터페이스는 Animal의 name 프로퍼티를 "doldol"이라는 문자열 리터럴 타입으로 재정의했습니다. 하지만 이때 재정의하는 타입은 원래 타입의 서브 타입이어야 합니다. 만약 name을 number로 재정의하려고 하면 오류가 발생합니다:
interface Dog extends Animal {
name: number; // ❌ 불가능
breed: string;
}
이유는 Dog가 Animal의 서브타입이 되어야 하는데, name의 타입이 호환되지 않기 때문입니다.
10. 타입 별칭을 확장하기
인터페이스는 타입 별칭으로 정의된 객체 타입도 확장할 수 있습니다:
type Animal = {
name: string;
color: string;
};
interface Dog extends Animal {
breed: string;
}
이렇게 하면 타입 별칭을 기반으로 새로운 인터페이스를 확장할 수 있습니다.
11. 다중 확장(Multiple Inheritance)
인터페이스는 다중 확장도 가능합니다. 여러 인터페이스를 동시에 확장하여 새로운 인터페이스를 만들 수 있습니다:
interface DogCat extends Dog, Cat {}
const dogCat: DogCat = {
name: "",
color: "",
breed: "",
isScratch: true,
};
DogCat 인터페이스는 Dog와 Cat을 동시에 확장하여 두 타입의 모든 프로퍼티를 가집니다.
12. 선언 병합(Declaration Merging)
타입 별칭은 동일한 스코프 내에 중복된 이름으로 선언할 수 없는 반면, 인터페이스는 선언 병합이 가능합니다. 즉, 동일한 이름의 인터페이스를 여러 번 선언하면, 타입스크립트가 이를 병합하여 하나의 타입으로 만듭니다:
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;
}
첫 번째 Person에서는 name 프로퍼티의 타입을 string으로, 두 번째에서는 number로 정의했기 때문에 타입 충돌이 발생합니다.
13. 정리
- 인터페이스와 타입 별칭은 객체의 타입을 정의하는 데 사용되며, 기능적으로 유사하지만 특정 용도에서 차이점이 있습니다.
- 인터페이스는 객체의 형태를 명확하게 정의하고 확장과 선언 병합이 용이합니다.
- 타입 별칭은 Union과 Intersection 타입을 정의하는 데 유리하며, 더 복잡한 타입 표현이 가능합니다.
- 선택적 프로퍼티와 읽기 전용 프로퍼티를 통해 객체의 속성을 좀 더 유연하게 혹은 안전하게 정의할 수 있습니다.
- 메서드 정의 및 오버로딩이 가능하며, 하이브리드 타입도 정의할 수 있습니다.
- 인터페이스 확장을 통해 중복된 코드를 줄이고, 유지보수를 더 쉽게 할 수 있습니다.
- 선언 병합을 통해 동일한 이름의 인터페이스를 병합하여 코드를 확장할 수 있습니다.
- 복잡한 타입을 정의할 때 인터페이스와 타입 별칭의 장단점을 잘 이해하고 적절히 사용하는 것이 중요합니다.
'TypeScript' 카테고리의 다른 글
JavaScript this정리 (0) | 2024.10.29 |
---|---|
자바스크립트, 타입스크립트 스코프,호이스팅 (2) | 2024.10.29 |
TypeScript 정리3 (함수와 타입) (0) | 2024.10.25 |
TypeScript 정리2 (이해하기) (3) | 2024.10.25 |
TypeScript 정리1 (1) | 2024.10.25 |