interface와 implements 를 사용한 class 타입 확인
interface 는 object 의 타입을 지정할 때 사용할 수 있다.
그리고 또 다른 용도로, class 의 타입을 확인할 때에도 사용할 수 있다.
이 때, implements 키워드도 필요하다.
아래의 예시를 보자.
class Car {
model: string;
price: number = 1000;
constructor(a: string) {
this.model = a;
}
}
let 붕붕이 = new Car('morning');
위의 Car 클래스에서 생성된 객체들은 model 과 price 속성을 가진다.
클래스가 특정 속성을 가지고 있는지 타입으로 확인하고 싶을 때,
interface 와 implements 키워드를 사용하여 아래와 같이 확인할 수 있다.
interface CarType {
model: string;
price: number;
}
class Car implements CarType {
model: string;
price: number = 1000;
constructor(a: string) {
this.model = a;
}
}
클래스 이름 오른쪽에 implements 를 쓰고 interface 이름을 쓰면
해당 클래스가 인터페이스에 있는 속성을 모두 가지고 있는지 확인할 수 있다.
속성이 빠져있다면 에러로 알려주게된다.
implements 로는 확인만 할 수 있다
implements 는 interface 에 들어있는 속성을 가지고 있는지 확인하라는 의미이다.
클래스에 대한 필드와 타입을 할당하거나 변형하는 역할을 하지 않는다.
interface CarType {
model: string;
tax: (price: number) => number;
}
class Car implements CarType {
model; // any 타입이 됨
tax(a) { // a 파라미터는 any 타입이 됨
return a * 0.1;
}
}
위 예제를 보자.
CarType 을 implements 한 것은 model 이 string 타입이라고 자동으로 반영되는 것이 아니라
클래스 내부에서 model 은 any 타입으로 처리된다.
implements 는 클래스의 타입을 체크만 해주며, 타입을 할당하는 것은 아니란 것을 알 수 있다.
implements 와 extends 의 차이점
implements 는 클래스에 특정 필드와 함수를 가지고 있는지 확인하고 싶을 때 사용한다.
클래스 간에 복잡하게 상속을 하는 경우, 어떤 필드와 함수가 포함되어 있는지 추론하기 어려울 때
implements 를 사용하여 타입 체크를 할 수 있다. (클래스를 많이 만들 때 일종의 가이드북 역할을 해준다)
class A extends B 는 B 에 있던 필드와 함수를 A 로 복사해준다.
extends 는 클래스 간에 상속을 구현할 때 사용된다.
implements 는 단지 타입 체크만 해주는 반면, extends 는 상속을 통해 실제 속성과 메서드를 복사한다.
Index Signature 란
Index signature 는 object 의 속성 이름과 값의 타입을 미리 지정할 수 있는 기능이다.
속성 이름을 미리 알 수 없거나, 속성이 많을 경우에 유용하다.
interface StringOnly {
[key: string]: string;
}
index signature 를 사용하려면 위와 같이 interface 에 [key: 타입] 형식을 추가한다.
key는 속성이름을 나타내고, 그 값의 타입을 지정할 수 있다.
따라서 다음과 같은 객체를 만들 수 있다.
let obj: StringOnly = {
name: 'kim',
age: '20',
location: 'seoul'
};
모든 속성의 값을 String 으로 제한했기 때문에, 만약 값의 타입이 다양해질 수 있는 상황에서는 제약이 될 수 있다.
Index Signature 과 다른 속성 함께 사용하기
Index signature 는 다른 속성과 함께 사용될 수 있다.
하지만 모든 속성이 그 index signature 와 일치해야한다.
예를 들어 아래의 예시를 보자
interface NotAllowed {
[key: string]: string;
age: number; // 에러 발생
}
모든 속성이 String 값을 가져야한다는 규칙을 세워놓았지만,
age 라는 속성은 number 값을 가지기 때문에 에러가 발생한다.
이 문제를 해결하려면 아래와 같이 작성할 수 있다.
interface Allowed {
[key: string]: string | number;
age: number; // 가능
}
배열 형태로 index signature 사용하기
interface StringArray {
[key: number]: string;
}
let obj: StringArray = {
0: 'kim',
1: '20',
2: 'seoul'
};
index signature 를 이용하면 객체를 배열처럼 사용할 수 있다.
위 StringArray 라는 interface는 number 타입의 key에 string 값을 할당하는 객체를 배열처럼 다룰 수 있도록 한다.
재귀적 index signature 사용하기
interface MyType {
'font-size' : {
'font-size' : {
'font-size' : number
}
}
}
let obj = {
'font-size' : {
'font-size' : {
'font-size' : 14
}
}
}
위 예제와 같이 object안에 object 안에 object 가 들어있는 경우를 생각해보자.
실제로 이런 경우가 많지는 않겠지만 이렇게 중첩된 object 들을 한번에 타입지정하고 싶으면 어떻게 해야할까?
해결 방법은 index signature를 아래와 같이 재귀적으로 사용하는 것이다.
interface MyType {
'font-size': MyType | number;
}
위 방식은 중첩된 구조를 간결하게 타입지정할 수 있다.
재귀적인 방식이라 object가 얼마나 깊어지든지 유연하게 대응할 수 있을 것이다.
ref: 코딩애플, Typescript 공식문서