感謝支援ayqy個人訂閱号,每周義務推送1篇(only unique one)原創精品博文,話題包括但不限于前端、Node、Android、數學(WebGL)、國文(課外書讀後感)、英語(文檔翻譯)
如果覺得弱水三千,一瓢太少,可以去 http://blog.ayqy.net 看個痛快
寫在前面
對于對象等複雜結構的類型,TypeScript的理念是鴨子類型(duck typing),即值的“形狀”:
Type-checking focuses on the shape that values have.
TypeScript裡,通過接口來描述複雜結構的類型,例如:
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
// 等價于
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
這裡接口的概念不同于其它語言,不必顯式實作,隻表示一種類型限制
一.對象
可選屬性
緊跟着屬性名的?表示可選,類似于正規表達式中?的含義,例如:
interface SquareConfig {
color?: string; // 顔色可選
width: number; // 寬度必填
}
聲明可選的意義在于,不使用沒關系,用的話限制其類型正确:
// 正确
let squareConfig: SquareConfig = {width: 10};
// 錯誤:Type '10' is not assignable to type 'string'.
squareConfig.color = 10;
特殊的:
// 錯誤:Type '{ colour: string; width: number; }' is not assignable to type 'SquareConfig'.
let a: SquareConfig = { colour: "red", width: 100 };
拼寫有誤的colour之是以能被發現,是因為會檢查對象字面量身上的多餘屬性:
If an object literal has any properties that the “target type” doesn’t have, you’ll get an error.
但這種檢查隻針對字面量,是以:
let squareOptions = { colour: "red", width: 100 };
// 正确,不檢查變量squareOptions身上的多餘屬性
let a: SquareConfig = squareOptions;
索引簽名
有些場景下無法确定屬性名稱,例如:
let cache: NetCache = {};
cache['http://example.com'] = 'response';
cache['http://example.com/second'] = 'response';
允許NetCache類型的對象具有任意多個名為字元串的屬性,此時可以通過索引簽名(index signature)來描述這種類型限制:
interface NetCache {
[propName: string]: string;
}
隻讀屬性
interface Point {
readonly x: number;
readonly y: number;
}
緊跟在屬性名前的readonly表示隻讀,與const限制一樣,修改隻讀屬性會抛出編譯錯誤:
let p1: Point = { x: 10, y: 20 };
// 錯誤:Cannot assign to 'x' because it is a read-only property.
p1.x = 5;
P.S.const與readonly的差別在于前者用來限制變量,後者用來限制屬性(變量聲明之外的場景)
特殊的,隻讀數組有一種特别的類型表示ReadonlyArray<T>:
let ro: ReadonlyArray<number> = [1, 2, 3, 4];
// 都會引發編譯報錯
ro[0] = 5;
ro.push(5);
ro.length = 1;
限制了其它所有會讓數組内容發生變化的方式,還去掉了原型上的修改方法(pop、push、reverse、shift等),是以不允許把隻讀數組指派給普通數組:
// Type 'ReadonlyArray<number>' is missing the following properties from type 'number[]': pop, push, reverse, shift, and 6 more.
let arr: number[] = ro;
P.S.非要指派的話,可以通過類型斷言來做(let a: number[] = ro as number[])
另外,readonly也可以結合索引簽名使用,例如:
interface NetCache {
readonly [propName: string]: string;
}
用來限制屬性值初始化之後無法修改:
let cache: NetCache = { 'http://example.com': 'response' };
// Index signature in type 'NetCache' only permits reading.
cache['url'] = 'response';
函數
接口也能用來表示函數類型,通過調用簽名(call signature)來描述:
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc = (source: string, subString: string) => {
let result = source.search(subString);
return result > -1;
};
函數類型會對2個東西進行檢查:
參數類型
傳回值類型
注意,參數名不必完全比對(不要求參數名一定是source和subString,按參數位置依次檢查)
二.數組
數組的類型也可以用接口表示,例如:
interface StringArray {
[index: number]: string;
}
let myArray: StringArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
沒錯,就是索引簽名,不僅能表示一類屬性,還能描述數組。之是以叫索引簽名,是因為它能夠描述可索引值的類型,例如StringArray表示能夠通過數值索引通路字元串值
注意,隻有兩種合法的索引簽名,分别是string和number,并且二者不能同時出現:
interface NotOkay {
// Numeric index type 'boolean' is not assignable to string index type 'string'.
[x: number]: boolean;
[x: string]: string;
}
這是因為JavaScript中數值索引會被轉換成字元串索引:
// JavaScript
const a = [1, 2, 3];
a[1] === a['1'] // true
三.類
與其它語言一樣,類與接口之間有實作(implements)關系:
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
接口就像一種協定(或者說是契約),用來描述類的公開成員:
Explicitly enforcing that a class meets a particular contract.
P.S.構造函數的類型也能用接口描述,具體見Difference between the static and instance sides of classes
四.接口繼承
接口可以通過繼承的方式來擴充,例如:
interface Shape {
color: string;
}
// 通過繼承獲得color屬性
interface Square extends Shape {
sideLength: number;
}
同樣,也支援多繼承:
interface PenStroke {
penWidth: number;
}
// 多繼承
interface Square extends Shape, PenStroke {
sideLength: number;
}
通過繼承建立的這種層級關系有助于組織有關聯的接口,實作拆分、複用
require('./utils');
delete require.cache[require.resolve('./utils')];
interface NodeRequireFunction {
/* tslint:disable-next-line:callable-types */
(id: string): any;
}
interface NodeRequire extends NodeRequireFunction {
resolve: RequireResolve;
cache: any;
extensions: NodeExtensions;
main: NodeModule | undefined;
}