天天看點

接口_TypeScript筆記3

感謝支援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;
}