본문 바로가기
스터디

[Typescript] 인터페이스

by rious275 2023. 6. 10.

첫 번째 인터페이스 (Our First Interface)

아래는 타입을 지정한 함수이다.

function printLabel(labeledObj: { label: string }) {...}

printLabel({ size: 10, label: "Size 10 Object" });

위 예제를 interface를 사용하면 아래와 같이 작성할 수 있다.

interface LabeledValue {
    label: string;
}

function printLabel(labeledObj: LabeledValue) {...}

printLabel({ size: 10, label: "Size 10 Object" });

선택적 프로퍼티 (Optional Properties)

interface의 모든 프로퍼티가 필요하지 않은 상황이 있다. 특정 조건에서만 존재하거나, 아예 없을 수도 있다. 이럴 때는 아래와 같이 물음표 기호를 사용한다.

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;
}

createSquare({ color: "black" });

읽기전용 프로퍼티 (Readonly Properties)

일부 프로퍼티는 처음 생성될 때만 수정 가능하고, 이후에는 수정할 수 없어야 한다. 해당 상황에서는 아래와 같이 지정할 수 있다.

interface Point {
    readonly x: number;
    readonly y: number;
}

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // X

ReadonlyArray<T> 타입으로 배열도 수정하 수 없도록 지정할 수 있다.

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // X
ro.push(5); // X

readonly는 프로퍼티에 사용하고, const는 변수에 사용한다.

초과 프로퍼티 검사 (Excess Property Checks)

지정한 타입 외에 다른 프로퍼티가 오게되면, Typescript는 코드에 버그가 있다고 판단한다.

interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
    // ...
}

let mySquare = createSquare({ colour: "red", width: 100 });  // X 에러

위 검사를 피하는 방법은 타입 단언을 사용하는 것이다.

let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

하지만 타입 단언보다는, 추가 프로퍼티가 있다고 확신한다면 문자열 인덱스 서명(string index signature)을 사용하는 것이 더 나은 방법이다.

interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}

위에서 방법을 제시했지만, 검사를 피하는 방법을 시도하는 것보다는 타입 정의 자체를 수정하는 것이 가장 좋다.

함수 타입 (Function Types)

함수 타입을 기술하기 위해, interface호출 서명(call signature)을 전달한다.

interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    let result = source.search(subString);
    return result > -1;
}

위와 같이 사용되며, 매개변수의 이름이 같을 필요는 없다.

let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
    let result = src.search(sub);
    return result > -1;  // boolean을 반환하지 않는다면, 에러가 난다.
}

인덱서블 타입 (Indexable Types)

함수 타입을 설명하는 방법과 유사하게, a[10]이나 ageMap["daniel"]처럼 타입을 인덱스로 기술할 수 있다. 인덱서블 타입은 인덱싱 시, 반환 유형과 함께 객체를 인덱싱하는 데 사용할 수 있는 타입을 기술하는 인덱스 시그니처(index signature)를 가지고 있다.

interface StringArray {
    [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

StringArray 인덱스 서명은 number로 색인화 되며 string을 반환할 것을 나타낸다.

"사전" 패턴을 기술하는데 강력한 방법이지만, 모든 프로퍼티가 반환 타입과 일치하도록 강제한다.

interface NumberDictionary {
    [index: string]: number;
    length: number;  // O
    name: string;    // X 에러
}

인터페이스 확장하기 (Extending Interfaces)

인터페이스도 클래스처럼 확장(extend)이 가능하며, 이는 재사용성에 유연함을 준다.

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = {} as Square;
square.color = "blue";
square.sideLength = 10;

하이브리드 타입 (Hybrid Types)

몇몇 타입의 조합으로 동작하는 객체를 볼 수 있는데, 아래는 그 예제이다.

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = (function (start: number) { }) as Counter;
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}