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을 반드시 알아야 합니다. 모르신다면 여기를 참고해 주세요!
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" });
변수에게 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[]'.
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;
};
Index
를 이용하면, map
처럼 동작하게 만들 수 있습니다.
// Indexable Interface
interface Indexable {
[index: string]: string;
}
let store: Indexable = {};
store['some index'] = 'some value';
store['key'] = 'value';
가능한 Index
type은 string
과 number
의 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.prop
은 obj['prop']
으로 접근할 수 있기 때문에, Indexable의 다른 property도 string 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'.
}
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
의 Public (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) {}
}
Class
의 Constructor
는 Static 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");
}
}
(서로 상충되는 내용이 없는) 여러 Interface로부터 동시에 상속받을 수 있습니다.
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
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가 protected
나 private
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!