Typescript 기초(3) – Interfaces

Typescript Handbook / Developer’s Record 를 참고했습니다.

기본

interface LabelledValue {
  label: string;
}

let myObj: LabelledValue = {
  label: "A"
}

let myObj2: LabelledValue = {
  label: "B",
  size: 10 // error : Object literal may only specify known properties, and 'size' does not exist in type 'LabelledValue'
}

Object에 바로 Interface 지정해서 쓰는거는 모양이 딱 맞아야 한다.

하지만, 함수의 인자값으로 들어갈때는 모양이 조금 안맞아도 된다.

interface LabelledValue {
  label: string;
}

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

let myObj = {
  size: 10,
  label: "A"
}

printLabel(myObj) // A

그런데 또…인자값에서 바로 object를 넣는거는 모양이 딱 맞아야 한다.으이구.

interface LabelledValue {
  label: string;
}

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

printLabel({size: 10,label: "A"}) // Object literal may only specify known properties, and 'size' does not exist in type 'LabelledValue'.

왜 이런 혼란스러운 규칙이 있는지 모르겠다. 그냥, 모양이 딱 맞아야 함수의 인자값으로 넣을 수 있다고 생각하는게 맘 편하겠다.

Optional

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

function createSquare(config: SquareConfig): {color: string, area: number} {
  let newSquare = {
    color: "white",
    area: 10
  }
  if ( config.color ) {
    newSquare.color = config.color;
  }
  if ( config.width ) {
    newSquare.area = config.width;
  }
  return newSquare;
}

let config: SquareConfig = {
  color: "black"
}

createSquare(config); // { color: 'black', area: 10 }

Read only

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

let p1: Point = {
  x: 10,
  y: 20
}

p1.x = 5 // Cannot assign to 'x' because it is a read-only property

ReadonlyArray<T>

let roa: ReadonlyArray<number> = [1,2,3,4];
roa[1] = 12; // error : Index signature in type 'readonly number[]' only permits reading.

number[] 로 재할당 가능하다.

let roa: ReadonlyArray<number> = [1,2,3,4];
let a: number[] = roa as number[];
a[1] = 100;
console.log(a); // [ 1, 100, 3, 4 ]

함수타입

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

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

하지만, 꼭 parameter이름이 같을 필요는 없다.

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

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

그리고 Type도 굳이 안적어 줘도 된다.

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

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

Indexable

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

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

console.log(myArray[0]);

하지만, [number]와 [string]이 공존할 수는 없다.

class Animal {
  name: string = "라쿤"
}

class Dog extends Animal {
  breed: string = "닭다리";
}

interface NotOkay {
  [x: number]: Animal // Numeric index type 'Animal' is not assignable to string index type 'Dog'.
  [y: string]: Dog;
}

let arr: NotOkay = {
  1: new Animal(),
  2: new Animal(),
  "1": new Dog(),
  "2": new Dog()
}

arr[1]을 했을때 1은 문자 1로도 바뀔 수 있으니까 뭘 돌려줘야하는지 모르기때문에 에러가 발생하는것 같다(추측).

클래스 타입

interface ClockInterface {
  currentTime: Date;
}

class Clock implements ClockInterface {
  currentTime: Date;
  constructor(h: number, m: number) {
    this.currentTime = new Date();
  }
}

method도 강제할 수 있다.

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date;
  constructor(h: number, m: number) {
    this.currentTime = new Date();
  }
  setTime(d: Date) {
    this.currentTime = d;
  }
}

Constructor도 강제 할 수 있다.

interface ClockInterface {
  tick(): void;
}

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

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

let ditialC = createClock(DigitalClock, 2, 40);
let analogC = createClock(AnalogClock, 2, 40);
ditialC.tick(); // beep beep
analogC.tick(); // tick tock tick tock

DigitalClockcreateClock함수의 인자값으로 넣기 위해서는 constructor의 모양이 ClockConstructor와 딱 맞아야 한다.

Extends

interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

let square: Square = {
  color: "black",
  sideLength: 100
};

다중 extends도 가능하다.

interface Shape {
  color: string;
}

interface PenStrock {
  penWidth: number;
}

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

let square: Square = {
  color: "black",
  sideLength: 100,
  penWidth: 50
};

클래스를 extends할 수도 있다.

class Control {
  public test: any;
}

interface SelectableControl extends Control {
  select(): void;
}

class Button implements SelectableControl {
  public test: any;
  select() {}
}

이런걸 어디다 쓰지…?

Hybrid(짬뽕)

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

function getCounter(): Counter {
  let counter = <Counter>function(start: number) { console.log(start) };
  counter.interval = 100;
  counter.reset = function() {};
  return counter;
}

let counter: Counter = getCounter();
counter(10) // 10
counter.reset();
counter.interval = 5.0

function이 함수이면서 Object이기 때문에 가능한것이다. 재미있는 점은, (start: number): string에 key가 없다는것!

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다