개발을 하면서 기존에 있던 라이브러리의 인터페이스를 override할 일이 존재한다.
커스텀 설정으로 인터페이스를 오버라이딩 하고 싶을 때 어떻게 해야 하는지 알아보자.
외부 라이브러리는 보통 확장가능하도록 interface 형태로 작성한다.
type과 달리 interface는 선언병합의 특성이 있기 때문에 사용하는 입장에서 확장하기 더 수월하기 때문이다
기본형태는 다음과 같다.
interface Person {
name: string;
}
interface Person {
age: number;
}
const person: Person = {
name: "Alice",
age: 30,
};
만약 interface에 메소드가 정의되어 있다면?
Typescript는 오버로드 된 함수를 생성해 병합한다. => 아래의 class에서는 log가 2가지 형태의 함수를 모두 만족할 수 있는 것이다.
interface Logger {
log(message: string): void;
}
interface Logger {
log(message: string, level: number): void;
}
class ConsoleLogger implements Logger {
log(message: string, level?: number): void {
if (level !== undefined) {
console.log(`Level ${level}: ${message}`);
} else {
console.log(message);
}
}
}
인터페이스는 동일한 이름에 네임스페이스와 병합되는 특징이 있다.
아래와 같은 Album 인터페이스는 동일한 이름의 Album namespace와 병합된다
병합되었기 때문에 create기능을 함께 사용 할 수 있다.
interface Album {
title: string;
}
namespace Album {
export function create(title: string): Album {
return { title };
}
}
const myAlbum = Album.create("Greatest Hits");
console.log(myAlbum.title); // Output: Greatest Hits
클래스는 생성자를 포함할 수 있으며, 내부에 실제 구현된 메서드와 속성을 가질 수 있습니다. 즉, 클래스 인스턴스를 생성하여 사용할 수 있다.
interface는 실제로 구현하지 않는다. => Class가 interface의 구현체라고 생각하면 된다.
interface는 다중상속이 가능하다.
클래스를 타입으로 사용하면, 해당 클래스의 구조가 인터페이스처럼 사용할 수 있다.
하지만, 클래스 자체는 구현을 포함하므로 인스턴스를 생성할 수 있는 반면, 인터페이스는 단순히 타입을 정의하는 데만 사용됩니다.
비슷하게 보일 수 있으나, 결국 클래스는 특정 행동과 상태를 정의하는 반면, 인터페이스는 구조를 정의하는 데 중점을 둡니다.
결론적으로는 Class또한 type으로 사용될때는 interface로 동일한 이름은 선언병합이 된다는 것이다.
TypeScript에서 node_modules/@types 디렉토리는 TypeScript 지원이 내장되지 않은 JavaScript 라이브러리에 대한 유형 정의를 제공하여 필수적인 역할을 한다.
JS라이브러리 중 TS를 따로 지원해주지 않은 라이브러리는 @types 패키지를 따로 설치해줘야 TS 환경에서 사용 가능하다. @types 만약 이를 지원하지 않는다면, 다른 방법을 찾아야 한다.
@types 에 정의된 프로젝트는 TS에서 자동으로 프로젝트에 포함한다.
npm install --save-dev @types/node
@types에 있는 모듈이 아니기 때문에, tsconfig.json에 따로 커스텀한 모듈 패키지를 사용한다고 명시해 주는 과정이 필요하다.
먼저 확장하고자 하는 외부라이브러리 인터페이스에 대한 동일한 이름의 모듈을 정의한다.
그 다음, global.d.ts 파일로 이를 만들고, TS 컴파일러에게 해당 모듈도 우리 application에서 사용 할 것이라고 명시 해줘야 한다.
기존에 프로젝트에서 type 파일을 생성했다면 굳이 추가 해 줄 필요가 없다. include 옵션에 d.ts옵션이 포함되어 있기 때문이다.
대부분이 잘못 알고 있는 것 중 하나가 프로젝트 내에서 type 파일을 만들게 되면 typeRoots에 추가해야 한다고 알고 있는데 include에 포함돼 있는 .d.ts 파일은 자동으로 typescript가 인식하므로 넣어줄 필요가 없다.
include 외에 있는 경우만 추가해주면 된다.
아래 두가지 옵션을 통해 외부 모듈의 존재를 TS 컴파일러에게 알려 줄 수 있다.
https://www.typescriptlang.org/tsconfig/#typeRoots
typeRoots를 통해서 지정한 type 경로는 커스텀 타입이 아니라 일반적으로 외부 라이브러리가 제공하는 모듈의 타입을 정의하기 위해서 사용한다.(즉 우리가 하려는 라이브러리 공통 커스텀이다)
tsconfig.json의 typeRoots 옵션은 TypeScript가 유형 정의를 찾아야 하는 하나 이상의 디렉터리를 지정하는 데 사용된다.
기본적으로 TypeScript는 가장 널리 사용되는 JavaScript 라이브러리에 대한 유형 정의가 설치된 node_modules/@types 디렉토리에서 type을 검색한다.
tsconfig.json에서 typeRoots 옵션을 설정하면 TypeScript는 typeRoots에 명시된 경로만 검색하여 type을 찾는다.
typeRoots에 대한 사용자 정의 디렉터리를 제공하는 경우 명시적으로 포함하지 않는 한 TypeScript는 더 이상 node_modules/@types를 자동으로 포함하지 않는다.
{
"compilerOptions": {
"typeRoots": ["./custom-types", "./node_modules/@types"]
}}
https://www.typescriptlang.org/tsconfig/#types
types 옵션을 사용하면 이름으로 유형 정의 배열을 지정할 수 있다.
이를 사용하면 TypeScript는 지정된 유형 정의만 포함하고 명시적으로 포함되지 않는 한 node_modules/@types 디렉토리의 다른 유형 정의를 무시한다. 즉 node_modules/@types에서 타입 범위를 제한시킨다.
{
"compilerOptions": {
"types": ["node", "jest"]
}}
{
"compilerOptions": {
"typeRoots": ["./custom-types", "./node_modules/@types"],
"types": ["my-library"]
}}
1.인터페이스 선언시 인터페이스 안에 인터페이스가 있는 구조일때, 선언병합시 어떤걸 바라볼까?
=> 모든 인터페이스가 선언병합된 이후 컴파일 됨 => 즉 모두 flat 한 형태의 인터페이스가 된다고 생각하면 됨.
=> 된다. Class자체도 type으로 사용 될 때는 interface로 트랜스파일된다.