[하루 30분 러닝 타입스크립트] 인터페이스

✨타입 별칭 vs 인터페이스

  1. 인터페이스는 병합(merge)할 수 있습니다. 내장된 전역 인터페이스 혹은 외부 코드를 사용할 때 유용합니다.
  2. 인터페이스는 클래스가 선언된 구조의 타입을 확인하는 데 사용할 수 있지만, 타입 별칭은 그럴 수 없습니다.
  3. 일반적으로 인터페이스가 더 빨리 작동합니다.

✨속성 타입

선택적 속성

interface Poet{
    born?: number; // ? 키워드로 선택적 속성 사용
    name: string;
};

읽기 전용 속성

interface Poet{
    readonly born: number; 
		// readonly 키워드를 이용하여 다른 값으로 설정할 수 없음을 나타냅니다.
    name: string;
};

const poet: Poet = {
    born: 1980,
    name: "ABC"
}

poet.born = 2000 // ERROR Cannot assign to 'born' because it is a read-only property.

함수와 메서드

interface HasBothFunctionTypes {
    property: () => string;
    property2: () => string;
    method(): string;
}
const hasBoth: HasBothFunctionTypes = {
    property(){
        return ""
    },
    property2: () => "",
    method(){
        return ""
    },
}
  1. 메서드는 readonly로 선언할 수 없지만, 속성은 가능합니다.
  2. 인터페이스의 병합과 일부 작업은 메서드와 속성을 다르게 처리합니다.
  3. 기본 함수가 this를 참조할 수 있다는 것을 알고 있다면 메서드를 사용하세요.
  4. 반대의 경우 속성 함수를 사용하세요.

호출 시그니처

호출 시그니처란 값을 함수처럼 호출하는 방식에 대한 타입 시스템 설명입니다.

type FunctionAlias = (input: string) => number;

interface CallSignature {
    (input: string): number; // 값을 함수처럼 호출하는 방식에 대한 타입 시스템 설명
}

const typedFunctionAlias: FunctionAlias = (input) => input.length;
const typedCallSignature: CallSignature = (input) => input.length;

호출 시그니처는 속성을 추가로 갖는 함수를 설명하는 데 사용할 수 있습니다.

interface FunctionWithCount{
    count: number;
    () : void;
}

let hasCallCount: FunctionWithCount;

function keepsTrackOfCalls() {
    keepsTrackOfCalls.count += 1;
    console.log(`called ${keepsTrackOfCalls.count} times`)
}
keepsTrackOfCalls.count = 0;

hasCallCount = keepsTrackOfCalls

인덱스 시그니처

interface WordCounts{
    [i: string]: number;
}

const counts: WordCounts = {};

counts.apple = 0; // OK
counts.banana = false; // ERROR Type 'boolean' is not assignable to type 'number'.

아래와 같은 상황에서 오류를 나타내지 못합니다.

따라서 키/값 쌍을 저장하는데 키 값을 미리 알 수 없다면, Map을 사용하는 편이 안전합니다.

interface DatesByName {
    [i: string]: Date;
}

const publishDates: DatesByName = {
    Frankenstein: new Date("1 January 1818")
}

publishDates.Frankenstein // Date 타입
console.log(publishDates.Frankenstein.toString())

publishDates.Abc // Date 타입 그러나 실제 값은 undefined.
console.log(publishDates.Abc.toString()) // 타입 오류는 나지 않지만 ERROR 발생

1️⃣ 속성과 인덱스 시그니처 혼합

명명된 속성에 대해 구체적인 속성 타입 리터럴을 사용할 수 있습니다.

interface HisToricalNovels {
    Oroonoko: 1688;
    [i: string]: number;
}

const novels: HisToricalNovels = {
    Oroonoko: 1688,
    Outlander: 1991,
}

const novels: HisToricalNovels = {
    Oroonoko: 1688,
    Outlander: 1991,
}

const notSameYear: HisToricalNovels = {
    Oroonoko: 1689, //ERROR Type '1689' is not assignable to type '1688'.
    Outlander: 1991,
}

const missingOrronoko: HisToricalNovels = {
    Outlander: 1991,
}
/* ERROR 
Property 'Oroonoko' is missing in type '{ Outlander: number; }'
but required in type 'HisToricalNovels'.*/

2️⃣숫자 인덱스 시그니처

숫제 인덱스 시그니처를 사용할 땐 다음과 같은 주의사항이 있다.

interface MoreNarrowNumbers {
    [i: number]: string;
    [i: string]: string | undefined
}

interface MoreNarrowStrings {
    [i: string]: string;
    [i: number]: string | undefined
    /*ERROR 
			'number' index type 'string | undefined' is not assignable 
				to 'string' index type 'string'.
		*/
}

✨ 인터페이스의 확장

타입스크립트는 다른 인터페이스의 멤버를 복사하여 선언할 수 있는 확장(extend) 기능을 허용합니다.

interface Writing {
    title: string;
}

interface Novel extends Writing {
    pages: number
}

const novel: Novel = {
    title: "ABC",
    pages: 123
}

재정의된 속성

파생 인터페이스는 기존 인터페이스의 속성을 재정의 하거나 대체할 수 있습니다.

interface Novel {
    title: string | null;
    pages: number
}

interface NewNovel extends Novel {
    title: string
}

const novel: NewNovel = {
    title: null, // ERROR Type 'null' is not assignable to type 'string'
    pages: 123
}

다중 인터페이스 확장

interface GivesNumber {
    giveNumber() : number;
}

interface GivesString {
    giveString() : string;
}

interface GivesBothAndEither extends GivesNumber, GivesString {
    giveBothAndEither() : string | number;
}

✨ 인터페이스 병합

동일한 이름, 동일한 스코프에 선언된 경우 병합됩니다.

interface Merged {
    first: string;
}

interface Merged {
    second: string;
}

const obj: Merged = {
    first: "A",
    second: "B"
}

1️⃣ 병합이 사용되는 경우?

일반적으로 병합은 혼란을 야기하기 때문에 자주 사용되지 않습니다.

하지만 외부 패키지나 Window 같은 내장 전역 인터페이스를 보강하는데 특히 유용합니다.

interface Window {
		myEnvironmentVariable: string;
}

이름이 충돌되는 멤버

나중에 병합된 인터페이스에도 동일한 타입이 사용되어야 합니다.

interface Merge {
    same: (input: boolean) => string;
    different: (input: string) => string;
}

interface Merge {
    same: (input: boolean) => string; // OK
    different: (input: boolean) => string; //ERROR
    /* Subsequent property declarations must have the same type.
      Property 'different' must be of type '(input: string) => string',
       but here has type '(input: boolean) => string'. */
}