본문 바로가기

TypeScript/Handbook

[TypeScript] 03. Interfaces

원문 : https://www.typescriptlang.org/docs/handbook/interfaces.html



TypeScript의 핵심 원칙(?)은 type-checking 이다. 이를 위해 Interface라는 녀석을 정의해서 사용한다.

간단한 코드를 통해서 알아보자.

function printLabel(labelledObj: { label: string }) {
    console.log(labelledObj.label);
}

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


type-checker는 printLabel 이라는 함수를 체크한다. printLabel 이라는 함수는 labelledObj 라는 매개변수를 받는데 이 녀석은 label 이라는 string 타입의 변수가 반드시 필요한 녀석이다. 매개변수가 다른 값들을 가질 수도 있겠지만 printLabel 이라는 함수를 실행하기 위한 매개변수는 반드시 label 이라는 string 타입 변수를 가지고 있어야 한다.


위 예제를 interface를 사용해서 다시 작성해보자.

interface LabelledValue {
    label: string;
}

function printLabel(labelledObj: LabelledValue) {
    console.log(labelledObj.label);
}

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

먼저 예제에서 처럼 string 타입의 label이라는 변수를 가지는 interface(LabelledValue)를 정의하고, printLabel 함수에서 이 interface 타입을 매개변수로 받아 처리한다. 이와같이 interface는 함수와 함수간, 코드와 코드간에 사용하기로하는 값들의 형태에 대한 계약(?) 같은 거라고 생각하면 될듯 하다.



Optional Properties

말 그대로 optional 한 값들을 정의하는 방법이다.

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

createSquare 함수는 SquareConfig 모양의 config 를 매개변수로 받아 color(string 타입), area(number 타입) 두변수를 가지는 값을 반환한다.

그런데 config는 color 이라는 값을 가질 수도 있고 안가질 수도 있다. width 라는 값도 가질수도, 안 가질 수도 있다.

이런경우 inteface 정의 부분에 변수명 뒤에 "?"를 붙여 optional 값으로 정의한다.

"어차피 있어도 되고 없어도 되는 값을 정의할거면 뭐하러 inteface를 쓰나?" 라고 생각 할 수도 있다.

하지만 함수내에서 color이라는 값을 colr 이라고 잘못쓴다던가 width를 with라고 잘못쓰는 실수들을 체크 할 수 있다. (그냥 javascript라면 colr, with 라는 신규 변수를 생성 할 것이다.) 



Readonly properties

수정이 불가한 읽기 전용 값들을 선언 할 수 있다.

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 = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
//위 코드를 실행하고 싶다면 아래와 같이 작성하면 된다.
a = ro as number[];


readonly vs const

변수에는 const를 쓰고 속성 값 에는 readonly를 쓰자



Excess Property Checks

위 예제에서 createSquare 함수는 SquareConfig라는 인터페이스를 매개변수로 받아서 처리하도록 정의했고, SqureConfig 인터페이스는 color, width라는 이름의 값들을 선택적으로 사용하기로 했다. 하지만 어떤이름의 값이 들어올지 미리 알 수 없는 경우에는 어떻게 할까?

//에러난다.
let mySquare3 = createSquare({color: "black", opacity: 0.5});

이런경우 아래와 같이 interface를 고치면 에러 가 사라진다. 

//interface를 고쳐보자
interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}

사실 왜 이렇게 하는지 잘 모르겠다... 왜냐하면...

let tt = {colr: 'black', opacity: 0.5};
let mySquare = createSquare(tt);
let mySquare2 = createSquare({color: "black", opacity: 0.5} as SquareConfig);

위와 같이 처리해도 잘 되기 때문이다.... 이유를 아시는분들은 댓글좀 달아주세요



Function Types

interface 는 function type으로 정의 할 수도 있다.

기본적인 형식은 아래와 같다.

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

let mySearch: SearchFunc;
//매개변수의 type 만 맞춰주면 된다. 매개변수 명까지 맞출필요는 없다.
//mySearch = function(src: string, subString: string) {
//매개변수의 type 을 안적어도 된다.
//mySearch = function(src, sub) {
mySearch = function(source: string, subString: string) {
    let result = src.search(subString);
    return result > -1;
}



Indexable Types

interface 는 index를 붙일 수 있는 값도 정의가 가능하다. index는 number 아니면 string이 올 수 있으며, 한 interface 안에 number와 string 두 종류의 index를 모두 가질 수는 없다. (내부적으로 number형 index는 string 형으로 변환되어 처리 되기때문에 number나 string 이나 결국 모두 string 형태로 처리된다.)

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

let array1: StringArray;
array1 = ["Bob", "Fred"];
let str: string = array1[0];

interface Dictionary {
  [x: string] : string;
}

let array2: Dictionary;
array2['first'] = 'Bob';
array2['second'] = 'Fred';


index를 가지는 값의 형태에 따라 해당 interface에서 기본적으로 갖는 값도 제한된다.

interface NumberDictionary {
    [index: string]: number; //여기가 number 형 이기 때문에,
    length: number;    // number 형이라 괜찮다.
    name: string;      // string 형이라 안된다. NumberDictionary['name']과 똑같기 때문에 number 형이 되어야 한다.
}


readonly로 읽기 전용 배열을 만들 수 있다.

interface ReadonlyStringArray {
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!



Calss Types

- Implementing an inteface

C#이나 Java 같이 interface를 구현하는 class에 대한 정의가 가능하다.

interface ClockInterface { currentTime: Date; setTime(d: Date); } class Clock implements ClockInterface { //interface에 정의된 내용들(currentTime, setTime)이 모두 구현되어야 한다. currentTime: Date; setTime(d: Date) { this.currentTime = d; } constructor(h: number, m: number) { } }


- Difference between the static and instance sides of classes

class 는 static side 와 instance side를 가진다. class 가 interface를 구현할때 instance side 만 체크를 하기 때문에 생성자를 구현하려고 하면 에러가 발생한다.

interface ClockConstructor {
	new (hour: number, minute: number);
}

class Clock implements ClockConstructor {
	currentTime: Date;
	constructor(h: number, m: number) { }
}

에러 내용 : Class 'Clock' incorrectly implements interface 'ClockConstructor'.

  Type 'Clock' provides no match for the signature 'new (hour: number, minute: number): any'

이런 경우 생성자를 위한 interface, instance side를 위한 interface를 분리 구현하고 생성자를 실행하는 함수를 정의하여 해결 할 수 있다.

interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
  tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
  return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
	constructor(h: number, m: number) { }
	tick() {
		console.log("beep beep");
	}
}
class AnalogClock implements ClockInterface {
	constructor(h: number, m: number) { }
	tick() {
		console.log("tick tock");
	}
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);



Extending Interfaces

interface 는 서로 상속할 수 있으며, 다중 상속이 가능하다.

interface Shape {
	color: string;
}

interface PenStroke {
	penWidth: number;
}

interface Square extends Shape, PenStroke {
	sideLength: number;
}

let square = {};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;



Hybrid Types

property를 가지는 함수를 정의 할 수 도 있다.

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

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

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;



반응형

'TypeScript > Handbook' 카테고리의 다른 글

[TypeScript] 05. Functions  (0) 2018.01.31
[TypeScript] 04. Classes  (0) 2017.07.07
[TypeScript] 02. 변수 선언  (0) 2017.04.25
[TypeScript] 01. Basic Types  (0) 2017.04.24
[TypeScript] 00. 시작하기  (0) 2017.04.21