TypeScript에서 interface는 객체의 구조를 말한다. → 객체의 형태를 설명하는 방법
클래스와 달리 인터페이스는 청사진으로 이용하지 않고 사용자 정의 타입
으로만 사용하게 된다.
인터페이스를 정의할 때도 첫글자는 대문자로 작성한다.
function printLabel(labelObj: { label: string }) {
console.log(labelObj.label);
}
let myObj = { size: 10, label: "크가가 10인 객체" };
printLabel(myObj);
myObj
는 printLabel
보다 더 많은 프로퍼티를 갖고 있지만, 컴파일러는 최소한 필요한 프로퍼티가 있는지와 타입이 잘 맞는지만 검사한다.
위 코드를 인터페이스를 사용하면 아래와 같다.
interface LabeledValue {
label: string;
}
function printLabel(labelObj: LabeledValue) {
console.log(labelObj.label);
}
let myObj = { size: 10, label: "크가가 10인 객체" };
printLabel(myObj);
인터페이스의 프로퍼티는 어떤 조건에서만 존재하거나 아예 없을 수도 있다.
선택적 프로퍼티들은 객체 안의 몇 개의 프로퍼티만 채워 함수를 전달하는 “option bags” 패턴을 만들 때 유용하다.
선택적 프로퍼티를 가지는 인터페이스는 다른 인터페이스와 비슷하게 작성되고, 선택적 프로퍼티는 선언에서 프로퍼티 이름 끝에 ?
를 붙여 표시한다.
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
let newSquare = { color: "white", area: 100 };
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({ color: "black" });
일부 프로퍼티들은 객체가 처음 생성될 때만 수정 가능해야한다.
프로퍼티 이름 앞에 readonly
를 넣어서 지정할 수 있다.
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
// p1.x = 5; -> error
TypeScript에서는 모든 변경 메서드가 제거된 Array<T>
와 동일한 ReadonlyArray<T>
타입이 제공된다.
→ 생성 후에 배열을 변경하지 않음을 보장할 수 있다.
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
// ro[0] = 12;
// ro.push(5);
// a = ro;
// => error
a = ro;
를 보면 ReadonlyArray
를 일반 배열에 재할당이 불가능한 것을 확인할 수 있다.
타입 단언으로 오버라이드 하는 것은 가능하다.
a = ro as number[];
💡
readonly
는 프로퍼티에 사용하고const
는 변수에 사용한다.
객체 리터럴은 다른 변수에 할당할 때나 인수로 전달할 때, 초과 프로퍼티 검사를 받는다.
요구하는 인터페이스에 명시되지 않는 프로퍼티를 가지고 있으면 에러가 발생한다.
→ 똑같은 객체를 변수에 할당한 뒤 전달하면 에러가 발생하지 않는다.
→ 타입 단언 as 문법을 이용하여 초과 프로퍼티 검사를 피할 수도 있다.
let mySquare = createSquare({ width:100, opacity: 0.5 } as SquareConfig);
특별한 경우에, 추가 프로퍼티가 있음이 확실하다면, 문자열 인덱스 서명을 추가하는 것이 더 나은 방법이다.
color와 width 프로퍼티 외의 다른 프로퍼티를 가질 수 있다면 다음과 같이 정의할 수 있다.
interface SquareConfig {
color?: string;
width?: number;
**[propName: string]: any;**
// 정확한 속성 이름/개수는 모르지만
// 문자열로 해석 가능한 속성 이름을 지녀야하고
// 해당 속성 값의 타입은 아무거나 올 수 있음
}
인덱스 서명의 타입은 string
또는 number
여야 한다.
⚠️ 인덱스로 할당할 값의 타입이 any
가 아닌 경우, 인터페이스에 명시된 모든 프로퍼티는 모두 해당 타입을 따라야 한다.
⚠️ 또한 선택적 프로퍼티를 사용한 경우, string | undefined
타입을 인덱스 키의 타입인 string
에 할당할 수 없다는 에러가 발생한다.
interface Person {
// name?: string; 에러
// age: number; 에러
favorite: string;
[propname: string]: string;
}
인터페이스는 객체 외에 함수 타입을 설명할 수 있다.
인터페이스로 함수 타입을 기술하기 위해, 인터페이스에 호출 서명(call signature)를 전달한다.
이는 매개변수 목록과 반환 타입만 주어진 함수 선언과 비슷하다.
→ 각 매개변수는 이름과 타입 모두 필요하다.
interface SearchFunc {
(source: string, subString: string): boolean;
}
한번 정의되면, 함수 타입 인터페이스는 다른 인터페이스처럼 사용할 수 있다.
let mySearch: SearchFunc;
// 매개변수의 이름이 정의한 interface와 같을 필요는 없다.
mySearch = function (source: string, subString: string) {
let result = source.search(subString);
return result > -1;
};
함수 매개변수들은 같은 위치에 대응되는 매개변수끼리 한번에 하나씩 검사한다.
만약 타입을 지정하고 싶지 않다면, SearchFunc
타입의 변수로 직접 함수 값이 할당되었기 때문에 TypeScript의 문맥상 타이핑(contextual typing)이 인수 타입을 추론할 수 있다.
함수 표현식이 boolean
이 아닌 다른 타입을 반환하면, SearchFunc
인터페이스에 정의된 반환 타입과 일치하지 않는다는 에러를 발생시킨다.
타입을 인덱스로 기술할 수 있다.
인덱서블 타입은 인덱싱 할 때 해당 반환 유형과 함께 객체를 인덱싱하는 데 사용할 수 있는 타입을 기술하는 인덱스 시그니처
를 가지고 있다.
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["boo", "foo"];
let myStr: string = myArray[0];
StringArray
의 인덱스 서명은 StringArray
가 number
로 색인화(indexed)되면 string
을 반환할 것을 나타낸다.
두 타입의 인덱서(indexer)를 모두 지원하는 것은 가능하지만, 숫자 인덱서에서 반환된 타입은 반드시 문자열 인덱서에서 반환된 타입의 하위 타입이어야 한다.
→ number
로 인덱싱 할 때, JavaScript는 실제로 객체를 인덱싱하기 전에 string
으로 변환하기 때문이다.
→ 100(number)로 인덱싱 하는 것은 “100”(string)로 인덱싱하는 것과 같기 때문에, 일관성이 있어야한다.
interface AnimalEx {
name: string;
}
interface Dog extends AnimalEx {
breed: string;
}
interface NotOk {
// [x: number]: AnimalEx; -> error 숫자형 문자열로 인덱싱을 하면 다른 타입의 AnimalEx를 얻게 될 것 입니다.
[x: string]: Dog;
}
문자열 인덱스 시그니처는 “사전” 패턴을 기술하는데 강력하지만, 모든 프로퍼티들이 반환 타입과 일치하도록 강제한다.
interface Number {
[index: string]: number;
length: number;
// name: string; -> error 'name'의 타입은 인덱서의 하위 타입이 아님
}
인덱스 시그니처가 프로퍼티 타입들의 합집합이라면 다른 타입의 프로퍼티들도 허용할 수 있다.
interface NumberOrString {
[index: string]: number | string;
length: number;
name: string;
}
인덱스의 할당을 막기 위해 인덱스 시그니처를 “읽기 전용”으로 만들 수 있다.
interface ReadonlyStringArr {
readonly [index: number]: string;
}
let myArr: ReadonlyStringArr = ["boo", "foo"];
// myArr[2] = "hi"; -> error
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
constructor(h: number, m: number) {}
}
setTime
처럼 클래스에 구현된 메서드를 인터페이스 안에서도 기술할 수 있다.
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) {}
인터페이스 확장은 인터페이스의 멤버를 다른 인터페이스에 복사하는 것을 가능하게 해주는데, 인터페이스를 재사용성 높은 컴포넌트로 쪼갤 때, 유연함을 제공해준다.
interface Shape {
color: string;
}
interface Square extends Shape {
sideLenth: number;
}
let square = {} as Square;
square.color = "blue";
square.sideLenth = 10;
인터페이스는 여러 인터페이스를 확장할 수 있어, 모든 인터페이스의 조합을 만들 수 있다.
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLenth: number;
}
let square = {} as Square;
square.color = "blue";
square.sideLenth = 10;
square.penWidth = 5.0;
interface Interface {
// 프로퍼티 선언
propertyName: type;
// 메서드 선언
methodName(parameter: type): returnType;
// 선택적 프로퍼티
optionalProperty?: type;
// 읽기 전용 프로퍼티
readonly readonlyProperty: type;
}
interface FnInterface {
// 함수 선언
(x: type, y: type): returnType;
}
아주 유익한 내용이네요!