TypeScript 인터페이스
TypeScript의 인터페이스에 대해 알아보겠습니다.
1. 인터페이스
인터페이스(Interface)는 일반적으로 타입 체크를 위해 사용되며 변수, 함수, 클래스에 사용할 수 있습니다. 인터페이스는 여러가지 타입의 프로퍼티로 새로운 타입을 정의하는 것과 유사하며, 정의된 인터페이스는 일관성을 유지하기 위해 내부에 선언된 프로퍼티 또는 메소드의 구현을 강제하는 특징이 있습니다.
인터페이스는 클래스와 유사하지만 인스턴스 생성이 불가능하고 모든 메소드는 추상 메소드로 이루어져 있습니다. 또한 인터페이스의 추상 메소드는 abstract 키워드를 사용하지 않는다는 특징이 있습니다. 또한 ES6에서 지원하지 않고 TypeScript에서만 지원합니다.
2. 인터페이스의 사용
인터페이스는 변수, 함수, 클래스에 사용할 수 있습니다. 각각의 특징에 대해 알아보겠습니다.
2.1. 변수에 사용하는 인터페이스
변수의 타입에 인터페이스를 사용하는 경우엔 변수는 해당 인터페이스의 프로퍼티를 준수하여 사용해야 합니다. 또한 함수의 파라미터 타입으로도 사용이 가능한데 이 경우에도 함수의 파라미터 타입에 지정한 인터페이스를 준수하는 파라미터를 전달하여야 합니다. 이렇게 사용하면 함수에 객체를 전달할 때 별도의 매개변수 체크를 생략할 수 있다는 장점이 있습니다.
interface Unit {
name: string;
attack: number;
defense: number;
}
const units: Unit[] = [];
function createUnit(unit: Unit) {
units.push(unit);
}
const groundUnit: Unit = {
name: 'Marine',
attack: 6,
defense: 0
};
createUnit(groundUnit);
2.2 함수에 사용하는 인터페이스
함수의 타입에 인터페이스를 사용하는 경우에는 인터페이스에 타입이 선언된 파라미터 리스트와 리턴 타입을 정의해주어야 합니다. 변수의 경우와 마찬가지로 함수 인터페이스를 구현하는 함수는 인터페이스를 준수하여 사용해야합니다.
interface UnitFunc {
(name: string, attack: number, defense: number): void;
}
const setUnit: UnitFunc = (name: string, attack: number, defense: number): void => {
console.log(`name: ${name}, attack: $, defense: $`);
};
2.3 클래스에 사용하는 인터페이스
클래스에 implements 키워드로 인터페이스를 선언하여 사용할 수 있습니다. 이러한 경우 해당 클래스는 선언한 인터페이스를 반드시 구현해야 합니다. 이러한 특징으로 인하여 인터페이스는 일관성을 유지 할 수 있는 장점을 갖게 됩니다.
interface Unit {
name: string;
attack: number;
defense: number;
createUnit(name: string, attack: number, defense: number): void;
}
class UnitClass implements Unit {
// 인터페이스 프로퍼티 구현
constructor(
public name: string,
public attack: number;
public defense: number;
) {}
// 인터페이스 추상 메소드 구현
function createUnit(name: string, attack: number, defense: number): void {
this.name = name;
this.attack = attack;
this.defense = defense;
console.log(`name: $, attack: $, defense: $`);
}
}
const unitClass = new UnitClass();
unitClass.createUnit('Marine', 6, 0);
3. 덕 타이핑
TypeScript에서 덕 타이핑(Duck Typing)은 어떤 클래스에서 인터페이스에서 명시한 메소드를 가지고 있을 때, 해당 클래스가 인터페이스를 구현한 것으로 간주하는 것을 의미합니다.
interface Duck {
quack(): void;
}
class FirstDuck implements Duck {
public quack(): void {
console.log('Quack!');
}
}
class SecondDuck {
public quack(): void {
console.log('Quack Quack!');
}
}
function makeNoise(duck: Duck): void {
duck.quack();
}
makeNoise(new FirstDuck()); // Quack!
makeNoise(new SecondDuck()); // Quack Quack!
위의 예제에서 Duck 인터페이스를 구현한 OneDuck 클래스는 makeNoise() 함수의 파라미터로 사용되는게 문제가 없어 보이지만, SecondDuck 클래스는 Duck 인터페이스를 구현하지 않았음에도 문제가 발생하지 않습니다.
이미 다른 클래스에서 인터페이스의 메소드가 구현된 경우, 또다른 클래스에서 구현해주지 않아도 해당 인터페이스의 메소드는 이미 구현된 것으로 간주되며 타입 체크를 생략할 수 있다는 특징을 확인할 수 있습니다.
이러한 덕 타이핑을 구조적 타이핑(Structural Typing)이라고도 하며 함수 뿐만 아니라 변수에 사용할 경우에도 적용됩니다.
4. 선택적 프로퍼티
인터페이스의 프로퍼티는 반드시 구현되어야 하지만 선택적으로 사용해야하는 경우가 있을 수 있습니다. 이러한 경우에 사용하는 것이 선택적 프로퍼티(Optional Property) 입니다. 프로퍼티명 뒤에 '?'를 붙이며 선택적 프로퍼티로 선언된 경우 구현시 생략하여도 에러가 발생하지 않습니다.
interface Unit {
name: string;
attack: number;
defense: number;
skill?: string;
age?: number;
height?: number;
}
const unit: Unit = {
name: 'Marine',
attack: 6,
defense: 0
}
5. 인터페이스의 상속
인터페이스는 extends 키워드를 이용하여 다중 인터페이스 상속하거나 클래스를 상속받아 확장할 수 있습니다.
interface Unit {
name: string;
attack: number;
defense: number;
}
interface GroundUnit {
skills: string[];
speed?: number;
}
interface Marine extends Unit, GroundUnit {}
const marine: Marine = {
name: 'Marine',
attack: 6,
defense: 0,
skills: ['Attack', 'Move', 'Stim Pack']
}
다음과 같이 인터페이스가 클래스를 상속받을 경우엔 클래스의 모든 멤버가 상속되지만 구현까지 상속되지는 않습니다.
class Unit {
constructor(
public name: string,
public attack: number,
public defense: number
) {}
}
interface Marine extends Unit {
skills: string[];
}
const marine: Marine = {
name: 'Marine',
attack: 6,
defense: 0,
skills: ['Attack', 'Move', 'Stim Pack']
}
이상으로 TypeScript의 인터페이스에 대해 알아봤습니다.
※ Reference
- 이웅모 지음, 『Angular Essentials』, 루비페이퍼(2018), p137 ~ p.143 4.6 인터페이스
- poiemaweb.com, 인터페이스, https://poiemaweb.com/typescript-interface
- hyunseob.github.io, TypeScript: 인터페이스(Interface), https://hyunseob.github.io/2016/10/17/typescript-interface/