duck typing / structural subtyping, focus on the shape that values have
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
Optional Properties
interface SquareConfig {
// 加个问号
color?: string;
width: number;
}
Readonly Properties
interface Point {
readonly x: number;
readonly y: number;
}
ReadonlyArray
the same as Array
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
// error! 因为 a 的类型有 mutating methods, 而 ro 的类型没有
// 可以使用 type assertion 来避免报错, a = ro as number[]
a = ro;
readonly vs const
variable 用 const
property 用 readonly
Express Property Checks
只有可选属性的话还是不够的, 下面这中情况就比较麻烦:
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
// ....
}
// error, 注意, colour, color
let mySquare = createSquare({ colour: 'red', width: 100 });
一般有三种方式这个问题:
- 使用 type assertion
let mySquare = createSquare({ colour: 'red', width: 100 } as SquareConfig);
- index signature, 更推荐使用这种方式, 详情看下面的 indexable types
// SquareConfig can have any number of properties,
// and as long as they aren’t color or width, their types don’t matter.
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
- 先将这个 config 对象赋值给一个变量, 最好不要滥用这种方式
// 这样声明 squareOptions, ts 是不会对 squareOptions 进行类型检查的
// 从而以一种看起来很怪异的方式让 createSquare(squareOptions) 摆脱了类型检查
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);
Function Types
interface 除了可以用来描述 object 之外, 还可以用来描述 function
用一种叫 call signature 的格式来描述 function, 长得很像函数声明:
// 定义
interface SearchFunc {
(source: string, subString: string): boolean;
}
// 使用
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;
}
// 因为已经为变量 mySearch 使用了 searchFunc 接口
// 所以 ts 会自动推导参数和返回值的类型, 不用你个个都标清楚
let mySearch: SearchFunc;
mySearch = function(src, sub) {
let result = src.search(sub);
return result > -1;
}
Indexable Types
对 a[10]
, ageMap['daniel']
这种可以用下标语法访问的对象, 抽象出的类型叫 indexable types
Indexable types have an index signature that describes the types we can use to index into the object
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
规定 indexable types 的值的类型都相同:
interface NumberDictionary {
[index: string]: number;
length: number; // ok, length is a number
name: string; // error, the type of 'name' is not a subtype of the indexer
}
支持的 index signature 有两种, string 和 number
但是就算写 obj[100] = xxx, js 也会把 100 转换成 string 类型的 ‘100’, 所以一起用的时候要注意
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;
}
和 readonly 搭配
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
Class Types
implementing an inteface
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
注意, 一般来说, interface 只检查 the pulic side of the class, 不会管 private 的
但也有例外, 看下面的 Interfaces Extending Classes
Difference between the static and instance sides of classes
一个类可以被分为两部分: static side 和 instance side
当一个 class implements 一个 interface的时候, interface 只对 instance side 进行检查
// 之所以为什么要这么设计需要多练习, 多思考
// 定义了一个 construct signature
interface ClockConstructor {
new (hour: number, minute: number);
}
// Type 'Clock' provides no match for the signature 'new (hour: number, minute: number): any'
// interface 只对 instance side 进行检查
// 然而 constructor 属于 static side, 所以它在 instance side 检查不到就抱怨了
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number) { }
}
// 变通一下
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
就像 class 可以 extends class 一样, interface 也可以 extends interface
都是为了复用
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
// 多继承
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
Hybrid Types
函数也是一个对象, 在 js 里往函数挂一些属性也很常见
所以 interface 也允许你随意组合
interface Counter {
(start: number): string; // function type
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
Interfaces Extending Classes
没错你眼睛没花 , inteface 也能继承 class
When an interface type extends a class type it inherits the members of the class but not their implementations. It is as if the interface had declared all of the members of the class without providing an implementation. Interfaces inherit even the private and protected members of a base class. This means that when you create an interface that extends a class with private or protected members, that interface type can only be implemented by that class or a subclass of it.
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
select() { }
}
// Error: Property 'state' is missing in type 'Image'.
// 就是只能由 Control 的子类来 implements SelectableControl
class Image implements SelectableControl {
select() { }
}
// 这样也是 ok 的, 记住 interface 只检查 shape
let text: SelectableControl = new TextBox()