아래는 name과 id를 가지는 객체를 각각 타입 별칭과 인터페이스로 구현한 예시
// 타입 별칭
type User = {
name: string;
id: number;
};
// 인터페이스
interface User {
name: string;
id: number;
}
📌 타입 별칭 뒤에는 세미콜론이 붙고 인터페이스에는 붙지 않는다. 이것은 세미콜론을 사용해 변수를 선언하는 것과 세미콜론 없이 클래스 또는 함수를 선언하는 것의 차이를 반영한것
타입별칭과 인터페이스는 비슷해보이지만 주요한 차이점이 몇 가지 있다.
👍 타입 별칭의 유니언 타입이 필요한게 아니라면 인터페이스 사용을 추천함
📌 속성 타입은 별칭 객체 타입에서도 사용할 수 있다.
:
앞에 ?
를 붙이면 인터페이스의 속성이 선택적 속성임을 나타낸다. interface Book {
author?: string; // 옵션 (생략 가능)
pages: number; // 필수
}
// OK
const ok: Book = {
author: "Rita Dove",
pages: 80,
}
const missing: Book = {
pages: 80
}
readonly
키워드를 사용해서 속성을 읽기 전용으로 만들 수 있다. readonly
는 타입 시스템에만 존재하며 인터페이스에서만 사용할 수 있다. interface Page {
readonly text: string;
}
function read(page: Page) {
// OK: 속성을 수정하지 않고 읽는것
console.log(page.text);
// Error: Cannot assign to 'text' because it is a read-only property.
page.text += "!";
}
인터페이스 멤버를 함수로 선언하는 두 가지 방법
1. 메서드 구문: member(): void
와 같이 객체의 멤버로 호출되는 함수로 선언
2. 속성 구문: member: () => void
와 같이 독립 함수와 동일하게 선언
interface HasBothFunctionTypes {
property: () => string;
method(): string;
}
const hasBoth: HasBothFunctionTypes = {
property: () => "",
method() {
return "";
},
};
hasBoth.property();
hasBoth.method();
?
를 사용해 선택적 속성 멤버로 나타낼 수 있다.
interface OptionalReadonlyFunctions {
optionalProperty?: () => string;
optionalMethod?(): string;
}
메서드와 속성 선언은 대부분 서로 교환해서 사용할 수 있으나 몇 가지 차이점이 있다.
readonly
로 선언할 수 없지만 속성은 가능함 호출 시그니처는 함수의 매개변수와 반환 타입을 지정한다.
// 둘 다 동일한 함수 매개변수와 반환 타입을 설명함
type FunctionAlias = (input: string) => number;
interface CallSignature {
(input: string): number;
}
const typeFunctionAlias: FunctionAlias = input => input.length; // OK
const typeCallSignature: CallSignature = input => input.length; // OK
호출 시그니처를 사용하면 사용자 정의 속성을 갖는 함수를 설명할 수 있다.
interface FunctionWithCount {
count: number;
(): void;
}
let hasCallCount: FunctionWithCount;
function keepsTrackOfCalls() {
keepsTrackOfCalls.count += 1;
console.log(`I've been called ${keepsTrackOfCalls.count} types!`);
}
keepsTrackOfCalls.count = 0;
hasCallCount = keepsTrackOfCalls; // OK
function doesNotHaveCount() {
console.log("No idea!");
}
hasCallCount = doesNotHaveCount;
// Error: Property 'count' is missing in type
// '() => void' but required in type 'FunctionWithCount'.
// WordCounts 인터페이스는 number 타입의 값을 갖는 모든 string 키를 허용한다.
interface WordCounts {
[i: string]: number;
}
const counts: WordCounts = {};
counts.apple = 0; // OK
counts.banana = 1; // OK
counts.cherry = false; // Error: Type 'boolean' is not assignable to type 'number'.
인덱스 시그니처는 명시적으로 명명된 속성과 포괄적인 용도의 string 인덱스 시그니처를 한 번에 포함할 수 있다.
interface HistoricalNovels {
Oroonoko: number;
[i: string]: number;
}
const novels: HistoricalNovels = {
Outlander: 1991,
Oroonoko: 1688,
};
const missingOroonoko: HistoricalNovels = {
Outlander: 1991,
};
// Error: Property 'Oroonoko' is missing in type '{ Outlander: number; }'
// but required in type 'HistoricalNovels'.
속성과 인덱스 시그니처를 혼합해서 사용하는 일반적인 타입 시스템 기법 중 하나는 인덱스 시그니처의 원시 속성보다 명명된 속성에 대해 더 구체적인 속성 타입 리터럴을 사용하는것
// ChapterStarts를 사용하는 모든 객체의 preface 속성은 반드시 0이어야 한다.
interface ChapterStarts {
preface: 0;
[i: string]: number;
}
const correctPreface: ChapterStarts = {
preface: 0,
night: 1,
shopping: 5,
};
const wrongPreface: ChapterStarts = {
preface: 1, // Error: Type '1' is not assignable to type '0'.
};
interface MoreNarrowNumbers {
[i: number]: string;
[i: string]: string | undefined;
}
const mixesNumbersAndString: MoreNarrowNumbers = {
0: '',
key1: '',
key2: undefined,
}
interface MoreNarrowStrings {
[i: number]: string | undefined; // Error:
// 'number' index type 'string | undefined'
// is not assignable to 'string' index type 'string'.
[i: string]: string;
}
인터페이스 타입도 자체 인터페이스 타입 또는 객체 타입을 속성으로 가질 수 있다.
interface Novel {
author: {
name: string;
};
setting: Setting;
}
interface Setting {
place: string;
year: number;
}
let myNovel: Novel;
// OK
myNovel = {
author: {
name: "Jane Austen",
},
setting: {
place: "England",
year: 1812,
},
};
myNovel = {
author: {
name: "Jane Austen",
},
setting: {
// Error: Property 'place' is missing in type
// '{ year: number; }' but required in type 'Setting'.
year: 1812,
},
};
extends
키워드를 사용해서 인터페이스를 확장할 수 있다.
interface Writing {
title: string;
}
interface Novella extends Writing {
pages: number;
}
// OK
let myNovella: Novella = {
pages: 190,
title: "Ethan Frome",
};
let missingPages: Novella = {
// Error: Property 'pages' is missing in type
// '{ title: string; }' but required in type 'Novella'.
title: "Ethan Frome",
};
파생 인터페이스는 다른 타입으로 속성을 다시 선언해 기본 인터페이스의 속성을 재정의하거나 대체할 수 있다.
interface A {
name: string | null | undefined;
}
interface B extends A {
name: string;
}
// Error: Interface 'C' incorrectly extends interface 'A'.
interface C extends A {
name: number | string;
}
// OK
interface C extends A {
name: null | string;
}
extends
키워드 위에 쉼표로 인터페이스 이름을 구분해서 사용한다. interface GivesNumber {
giveNumber(): number;
}
interface GivesString {
giveString(): string;
}
interface GivesBothAndEither extends GivesNumber, GivesString {
giveEither(): number | string;
}
function useGivesBoth(instance: GivesBothAndEither) {
instance.giveEither(); // type: string | number
instance.giveNumber(); // type: number
instance.giveString(); // type: string
}
interface Merged {
fromFirst: string;
}
interface Merged {
fromSecond: number;
}
// 다음과 같음:
interface Merged {
fromFirst: string;
fromSecond: number;
}
interface MergedProperties {
same: (input: boolean) => string;
different: (input: string) => string;
}
// same은 속성이 모두 동일해서 문제가 없다.
// different는 input의 타입이 서로 다르기 때문에 오류가 발생한다.
interface MergedProperties {
same: (input: boolean) => string; // OK
different: (input: number) => string; // Error: Subsequent property declarations must have the same type.
}
interface MergedMethods {
different(input: string): string;
}
interface MergedMethods {
different(input: number): string; // OK
}