Interface와 Excess Property Check

White Piano·2020년 11월 10일
3

TypeScript Handbook

목록 보기
3/3

Interfaces의 일부를 요약 및 정리한 글입니다.

Interfaces

// "interface" keyword를 사용해서 정의할 수 있습니다.
interface LabeledValue {
  label: string;
}

function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);  // Structural Type System!!

TS를 이해하려면 Structural Type System을 반드시 알아야 합니다. 모르신다면 여기를 참고해 주세요!

Optional Properties

property 이름에 ? 접미를 사용하면 Optional로 정의됩니다. property가 특정 상황에만 필요할 경우 유용합니다.

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 properties

변수에게 const가 있다면, property에겐 readonly가 있습니다. readonly로 정의된 property는 수정이 불가능합니다.

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

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

TS는 readonly Array를 위해, 값을 수정할 수 있는 모든 method를 제거한 readonly <T> [] Type을 제공합니다.

let a: number[] = [1, 2, 3, 4];
let ro: readonly number[] = a;

ro[0] = 12;       // error: Index signature in type 'readonly number[]' only permits reading.
ro.push(5);       // error: Property 'push' does not exist on type 'readonly number[]'.
ro.length = 100;  // error: Cannot assign to 'length' because it is a read-only property.
a = ro;           // error: The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.

Function Types

interface에 (...parameters) property를 정의하면 함수 type으로 사용할 수 있습니다.

// 함수 Interface 정의
interface SearchFunc {
  (source: string, subString: string): boolean;
}

// 다른 Interface와 똑같이 사용할 수 있습니다!
let mySearch: SearchFunc;

// parameter의 이름이 동일하지 않아도, 순서만 지켜주시면 괜찮습니다!
mySearch = function (src, sub) {
  let result = src.search(sub);
  return result > -1;
};

Indexable Types

Index를 이용하면, map처럼 동작하게 만들 수 있습니다.

// Indexable Interface
interface Indexable {
  [index: string]: string;
}

let store: Indexable = {};
store['some index'] = 'some value';
store['key'] = 'value';

가능한 Index type은 stringnumber의 2가지입니다. 다만 number의 경우 내부적으로 string으로 변경되기 때문에, Numeric index의 반환형은 string index의 반환형도 만족해야 합니다.

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

// 잘못된 Interface
interface Indexable {
  [index: string]: Dog;
  [index: number]: Animal; // error: Numeric index type 'Animal' is not assignable to string index type 'Dog'.
}

obj.propobj['prop']으로 접근할 수 있기 때문에, Indexable의 다른 propertystring index반환형만족해야 합니다.

interface NumberDictionary {
  [index: string]: number;
  length: number; // ok, length is a number
  name: string;   // error: Property 'name' of type 'string' is not assignable to string index type 'number'.
}

Excess Property Checks

Structural Typing추가적인 property를 무시합니다. Optional Property는 일부 property가 없어도 괜찮습니다. 이 둘이 합쳐져서 하나의 골치 아픈(?) 문제를 만들었습니다. 바로 Optional Property에 오타가 있어도 오류로 인식하지 못하는 것입니다. 때문에 TS는 Object Literal에 한해서 Excess Property Check를 진행합니다.

interface Input {
  src: string;
  opt?: {};
}
function foo(input: Input) {}
foo({ src: 'source', additional: 'additional' }); // error! (Excess Property Check!)
// error: Argument of type '{ src: string; additional: string; }' is not assignable to parameter of type 'Input'.
//        Object literal may only specify known properties, and 'additional' does not exist in type 'Input'.

Class Types

ClassPublic (not private), Instance (not static) 영역에 Interface를 적용할 수 있습니다.

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) {}
}

Constructor

ClassConstructorStatic side이기 때문에, Type Check를 위해서는 별도의 Interface가 필요합니다.

// "Constructor" Interface
interface ClockConstructor {
  // "new" keyword에 주의하세요!
  new (hour: number, minute: number): ClockInterface;
}

// "Instance" Interface
interface ClockInterface {
  tick(): void;
}

// "class expression"
const Clock: ClockConstructor = class Clock implements ClockInterface {
  constructor(h: number, m: number) {}
  tick() {
    console.log("beep beep");
  }
}

Extends

Extending Interfaces

(서로 상충되는 내용이 없는) 여러 Interface로부터 동시에 상속받을 수 있습니다.

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

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

Interfaces Extending Classes

Interface도 Class로부터 상속이 가능합니다. 다만 Class가 아닌 Interface이기 때문에, (이미 구현된 부모의 member를 사용하는 Class와는 다르게) 상속받은 member들을 직접 구현해야 합니다.

class Base {
  foo: any;
}

interface ExtendingClasses extends Base {
  func(): any;
}

class Child implements ExtendingClasses {
  foo: any;
  func() {}
}

재밌게도, 상속한 Class가 protectedprivate member를 가지고 있을 경우, 오직 해당 Class나 해당 Class의 자식 Class에만 적용 가능한 Interface가 됩니다.

class Base {
  private foo: any;
}

interface ExtendingClasses extends Base {
  func(): any;
}

class FakeChild implements ExtendingClasses {
  private foo: any;
  func() {}
}
// error: Class 'FakeChild' incorrectly implements interface 'ExtendingClasses'.
//        Types have separate declarations of a private property 'foo'.

class GenuineChild extends Base implements ExtendingClasses {
  func() {}
} // OK

심지어 완전히 동일한 구조라도, 부모가 같지 않다면(혹은 부모 자신이 아니라면), 상속이 불가능합니다.

class FakeBase {
  private foo: any;
}

class FakeChild extends FakeBase implements ExtendingClasses {
  func() {}
} // error!

0개의 댓글