typescript interface 중

Interfaces

TypeScript Interface 설명

목차

  1. Excess preperty checks

  2. Function types

  3. Indexable types


  1. Class Types

  2. Extending interfaces

  3. Hybrid Types

  4. Interfaces extending Classes

Excess Property Checks

우리의 첫번째 interface 예시에서 타입스크립트는, 인터페이스에서는 { label: string}만 요구하는데 함수 인자값으로 { size: number, label: string}인 객체를 넘겼다. 우리는 또한 optional property에 대해 배웠고 이것이 얼마나 option bags라는 것을 구현하기에 유용한지 배웠다.

그러나 순진하게 두가지를 모두 섞는다면 JS에서 했던 실수를 똑같이 할 것이다. 위와 같은 예제의 createSquare로 다시 설명한다면:

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

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

let mySquare = creatSquare({ colour: 'red', wdith:100});

인자값으로 오타가 들어갔다는 것을 주의깊게 보라. (color 대신에 colour가 들어갔다.) 그냥 javascript에서는 조용히 실패를 하게 될 것이다.

당신은 이 프로그램이 제대로 타입되었다고 예기할 수도 있다. 왜냐하면 width prop이 맞는 방식으로 들어갔기 때문이다. 그리고 추가적으로 color는 존재하지 않고 colour prop은 중요하지 않기 때문이다.

그러나 타입스크립트는 이코드에서 에러를 발생시킨다. Object literals은 특별한 취급을 갖고 excess property checking을 실행한다. 이것은 변수에 할당하거나 인자값으로 넘길때 발생하는데, 만약 interface에 없는 값을 직접 Object literals로 추가하려고 할경우 타입스크립트는 허용하지 않을 것이다.

// error: 'colour' not expected in type 'SquareConfig'
let mySquare = createSquare({
    colour: 'red',
    width: 100
});

이런 류의 에러는 인자값을 넣을 때 원하는 인터페이스로 타입캐스팅을 하기만 하면 빠져나갈 수있다.

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

더 나은 방법은 애초에 inteface를 만들때 더 propName을 추가 할 수 있도록 구현하는 것이다. 이것을 index signature를 추가한다고 하며 아래와 같이 구현한다.

interfacet SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any; //이제 어떤 prop이든 추가할 수 있다.
}

위와 같이 구현하면 몇개의 prop이든 어떤 값이든 추가가 가능하며 color와 width만 타입의 조건을 충족만 시킨다면 통과시킨다.

마지막으로 excess property check를 벗어나는 방법은 인자로 넘길 객체를 다른 객체에 할당한 후에 인자값으로 넘기는 방법이다. 이렇게 할경우 에러를 발생시키지 않는다.

let squareOptions = { colour: 'red', width: 100};
let mySquare = createSquare(squareOptions); //no error

위와같은 코드는 주로 의도치 않은상황에서 발생한다. 더 복잡한 메서드나 상태를 지니고 있는 object literals의 경우에는 당신은 이런 태크닉에 대해 알고 있어야 한다. 왜냐하면 대부분의 object literal 에러는 버그이기 때문이다. 웬만하면 정의되지 않은 값을 넘기는 것보다는 interface의 재정의를 할 것을 추천한다.

Function Types

Interface는 javascript의 다양한 형태를 구현할 수 있을 뿐 아니라 함수타입 역시 구현이 가능하다. 함수타입을 interface에 구현하기 위해, 우리는 call signature라는 것을 주었다. 이것은 함수 선언과 같은데 함수의 인자값으로 어떤 값이 들어값이 어떤 타입을 가지고 있을 것인가와 반환값의 타입이 무엇일 것인지를 명시해 줌으로써 함수 타입이라는 것을 선언한다. 아래 코드와 같이 함수를 선언해준다.

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

한번 선언되면, 다른 인터페이스처럼 사용할 수가 있다. 여기서 우리는 어떻게 변수에 function 타입을 만들고, 같은 타입의 function을 할당하는지 보여줄 것이다.

let mySearch: SearchFunc;

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

반드시 처음에 interface에 선언한대로 인자값이 들어갈 필요는 없다. 우리는 아래와 같이 함수를 할당 할 수 있다.

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

만약 인자값으로 src나 sub에 다른 타입을 넘기려고 하거나 리턴값으로 boolean아 아닌 다른 값을 반환할려고 한다면 에러를 발생시킬 것이다. 왜냐하면 인자값에 type어 어떤 것이 사용되었고 interface에 선언된 함수타입 선언과 일치하는지 하나하나 컴파일러가 확인할 것이기 때문이다.

Indexable Types

함수를 인터페이스에 어떻게 사용할지와 비슷하게 우리는 또한 a[10] 또는 ageMap[‘daniel’]과 같이 인덱싱할 수있는 타입을 구현할 수있다. Indexable Type들은 인덱싱할 때 사응하는 리턴타입에 따라 _index signature_를 가진다. 어려운 설명인 것 같다. 아래의 예시를 보며 설명하겠다.

interface StringArray {
    [index: number]: string; //반환하는 타입이 무엇인가.
}

let myArray: StringArray;
myArray = ['bob', 'Fred']; //배열일지 모르지만 사실 기본적으로 객체이다.

let myStr: string = myArray[0];

위의 예제에서 StringArray interface는 index signature 를 가지고 있다. StringArray가 number로 index 될 때 string을 반환할 것이다.

주석 : 저게 어떻게 되나 싶지만 사실 알고보면 배열은 이렇게 생긴 객체다

{
    '1' : 'bob,
    '2' : 'fred'         
}

저렇게 인덱싱 가능하게 interface를 만들어 놓으면 몇개의 값이든 더 들어갈 수 있되, 숫자로 인덱싱이 가능하다.

string과 number 오직 2가지타입만 인덱시에서 지원한다. 하지만 결국 숫자로 index 되어도 string과 같다는 것을 기억해야한다. javascript는 사실 오브젝트로 인덱싱하기 전에 숫자를 문자열로 변환된다. 아래의 예제를 보자.

class Animal {
    name: string;
}

class Dog extends Animal{
    breed: string;
}

//Error: indexing with a numeric string might get you a completely separate type of Animal!
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}

let animal: Animal = new Animal();
let dog: Dog = new Dog();

let somethings: NotOkay = {

    [0]: animal // -> error
    //[0]: dog -> ok 
} 

위의 코드가 에러인 이유는 숫자로 인덱싱해도 사실상 문자열로 indexing하는 것과 동일한데, 0으로 인덱싱 하자니 숫자 인덱싱이여서 Animal을 반환해야 한다고 생각되지만, 사실은 문자열이니 반환값은 Dog class여야 하기 때문이다. 그래서 Dog가 반환되어야 하기때문에 number로 인덱싱할 경우 문제가 생긴다. 하지만 Dog를 숫자로 인덱싱 할 경우 문제가 없는데, 0이 문자열로 인덱싱 되어 Dog class가 반환되는 것이 맞기때문이다.

마지막으로 index signature를 readonly를 입힐 수 입혀 변경을 방지할 수 있다.

interface ReadonlyStringArray {
    readonly [index: number]: string;
}

let myArray: ReadonlyStringArray = ['Alice', 'Bob'];
myArray[2] = 'Mallory'; // -> error!

위와 같이 myArray에 다른 값을 할당하는것은 error를 발생 시킨다.

Class Types

Extending Interfaces

Hybrid Types

Interfaces Extending Classes

1 Like