TypeScript——高級(jí)類型(1)

交叉類型(Intersection Types)

交叉類型是將多個(gè)類型合并為一個(gè)類型。 這讓我們可以把現(xiàn)有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。 例如, Person & Serializable & Loggable同時(shí)是 Person 和 Serializable 和 Loggable。 就是說這個(gè)類型的對象同時(shí)擁有了這三種類型的成員。

我們大多是在混入(mixins)或其它不適合典型面向?qū)ο竽P偷牡胤娇吹浇徊骖愋偷氖褂谩?(在JavaScript里發(fā)生這種情況的場合很多!) 下面是如何創(chuàng)建混入的一個(gè)簡單例子:

function extend<T, U>(first: T, second: U): T & U {

? ? let result = <T & U>{};

? ? for (let id in first) {

? ? ? ? (<any>result)[id] = (<any>first)[id];

? ? }

? ? for (let id in second) {

? ? ? ? if (!result.hasOwnProperty(id)) {

? ? ? ? ? ? (<any>result)[id] = (<any>second)[id];

? ? ? ? }

? ? }

? ? return result;

}

class Person {

? ? constructor(public name: string) { }

}

interface Loggable {

? ? log(): void;

}

class ConsoleLogger implements Loggable {

? ? log() {

? ? ? ? // ...

? ? }

}

var jim = extend(new Person("Jim"), new ConsoleLogger());

var n = jim.name;

jim.log();

聯(lián)合類型(Union Types)

聯(lián)合類型與交叉類型很有關(guān)聯(lián),但是使用上卻完全不同。 偶爾你會(huì)遇到這種情況,一個(gè)代碼庫希望傳入 number或 string類型的參數(shù)。 例如下面的函數(shù):

/**

* Takes a string and adds "padding" to the left.

* If 'padding' is a string, then 'padding' is appended to the left side.

* If 'padding' is a number, then that number of spaces is added to the left side.

*/

function padLeft(value: string, padding: any) {

? ? if (typeof padding === "number") {

? ? ? ? return Array(padding + 1).join(" ") + value;

? ? }

? ? if (typeof padding === "string") {

? ? ? ? return padding + value;

? ? }

? ? throw new Error(`Expected string or number, got '${padding}'.`);

}

padLeft("Hello world", 4); // returns "? ? Hello world"

padLeft存在一個(gè)問題, padding參數(shù)的類型指定成了 any。 這就是說我們可以傳入一個(gè)既不是 number也不是 string類型的參數(shù),但是TypeScript卻不報(bào)錯(cuò)。

let indentedString = padLeft("Hello world", true); // 編譯階段通過,運(yùn)行時(shí)報(bào)錯(cuò)

在傳統(tǒng)的面向?qū)ο笳Z言里,我們可能會(huì)將這兩種類型抽象成有層級(jí)的類型。 這么做顯然是非常清晰的,但同時(shí)也存在了過度設(shè)計(jì)。 padLeft原始版本的好處之一是允許我們傳入原始類型。 這樣做的話使用起來既簡單又方便。 如果我們就是想使用已經(jīng)存在的函數(shù)的話,這種新的方式就不適用了。

代替 any, 我們可以使用 聯(lián)合類型做為 padding的參數(shù):

/**

* Takes a string and adds "padding" to the left.

* If 'padding' is a string, then 'padding' is appended to the left side.

* If 'padding' is a number, then that number of spaces is added to the left side.

*/

function padLeft(value: string, padding: string | number) {

? ? // ...

}

let indentedString = padLeft("Hello world", true); // errors during compilation

聯(lián)合類型表示一個(gè)值可以是幾種類型之一。 我們用豎線( |)分隔每個(gè)類型,所以 number | string | boolean表示一個(gè)值可以是 number, string,或 boolean。

如果一個(gè)值是聯(lián)合類型,我們只能訪問此聯(lián)合類型的所有類型里共有的成員。

interface Bird {

? ? fly();

? ? layEggs();

}

interface Fish {

? ? swim();

? ? layEggs();

}

function getSmallPet(): Fish | Bird {

? ? // ...

}

let pet = getSmallPet();

pet.layEggs(); // okay

pet.swim();? ? // errors

這里的聯(lián)合類型可能有點(diǎn)復(fù)雜,但是你很容易就習(xí)慣了。 如果一個(gè)值的類型是 A | B,我們能夠 確定的是它包含了 A 和 B中共有的成員。 這個(gè)例子里, Bird具有一個(gè) fly成員。 我們不能確定一個(gè) Bird | Fish類型的變量是否有 fly方法。 如果變量在運(yùn)行時(shí)是 Fish類型,那么調(diào)用 pet.fly()就出錯(cuò)了。

類型保護(hù)與區(qū)分類型(Type Guards and Differentiating Types)

聯(lián)合類型適合于那些值可以為不同類型的情況。 但當(dāng)我們想確切地了解是否為 Fish時(shí)怎么辦? JavaScript里常用來區(qū)分2個(gè)可能值的方法是檢查成員是否存在。 如之前提及的,我們只能訪問聯(lián)合類型中共同擁有的成員。

let pet = getSmallPet();

// 每一個(gè)成員訪問都會(huì)報(bào)錯(cuò)

if (pet.swim) {

? ? pet.swim();

}

else if (pet.fly) {

? ? pet.fly();

}

為了讓這段代碼工作,我們要使用類型斷言:

let pet = getSmallPet();

if ((<Fish>pet).swim) {

? ? (<Fish>pet).swim();

}

else {

? ? (<Bird>pet).fly();

}

用戶自定義的類型保護(hù)

這里可以注意到我們不得不多次使用類型斷言。 假若我們一旦檢查過類型,就能在之后的每個(gè)分支里清楚地知道 pet的類型的話就好了。

TypeScript里的 類型保護(hù)機(jī)制讓它成為了現(xiàn)實(shí)。 類型保護(hù)就是一些表達(dá)式,它們會(huì)在運(yùn)行時(shí)檢查以確保在某個(gè)作用域里的類型。 要定義一個(gè)類型保護(hù),我們只要簡單地定義一個(gè)函數(shù),它的返回值是一個(gè) 類型謂詞:

function isFish(pet: Fish | Bird): pet is Fish {

? ? return (<Fish>pet).swim !== undefined;

}

在這個(gè)例子里, pet is Fish就是類型謂詞。 謂詞為 parameterName is Type這種形式, parameterName必須是來自于當(dāng)前函數(shù)簽名里的一個(gè)參數(shù)名。

每當(dāng)使用一些變量調(diào)用 isFish時(shí),TypeScript會(huì)將變量縮減為那個(gè)具體的類型,只要這個(gè)類型與變量的原始類型是兼容的。

// 'swim' 和 'fly' 調(diào)用都沒有問題了

if (isFish(pet)) {

? ? pet.swim();

}

else {

? ? pet.fly();

}

注意TypeScript不僅知道在 if分支里 pet是 Fish類型; 它還清楚在 else分支里,一定 不是 Fish類型,一定是 Bird類型。

typeof類型保護(hù)

現(xiàn)在我們回過頭來看看怎么使用聯(lián)合類型書寫 padLeft代碼。 我們可以像下面這樣利用類型斷言來寫:

function isNumber(x: any): x is number {

? ? return typeof x === "number";

}

function isString(x: any): x is string {

? ? return typeof x === "string";

}

function padLeft(value: string, padding: string | number) {

? ? if (isNumber(padding)) {

? ? ? ? return Array(padding + 1).join(" ") + value;

? ? }

? ? if (isString(padding)) {

? ? ? ? return padding + value;

? ? }

? ? throw new Error(`Expected string or number, got '${padding}'.`);

}

然而,必須要定義一個(gè)函數(shù)來判斷類型是否是原始類型,這太痛苦了。 幸運(yùn)的是,現(xiàn)在我們不必將 typeof x === "number"抽象成一個(gè)函數(shù),因?yàn)門ypeScript可以將它識(shí)別為一個(gè)類型保護(hù)。 也就是說我們可以直接在代碼里檢查類型了。

function padLeft(value: string, padding: string | number) {

? ? if (typeof padding === "number") {

? ? ? ? return Array(padding + 1).join(" ") + value;

? ? }

? ? if (typeof padding === "string") {

? ? ? ? return padding + value;

? ? }

? ? throw new Error(`Expected string or number, got '${padding}'.`);

}

這些* typeof類型保護(hù)*只有兩種形式能被識(shí)別: typeof v === "typename"和 typeof v !== "typename", "typename"必須是 "number", "string", "boolean"或 "symbol"。 但是TypeScript并不會(huì)阻止你與其它字符串比較,語言不會(huì)把那些表達(dá)式識(shí)別為類型保護(hù)。

instanceof類型保護(hù)

如果你已經(jīng)閱讀了 typeof類型保護(hù)并且對JavaScript里的 instanceof操作符熟悉的話,你可能已經(jīng)猜到了這節(jié)要講的內(nèi)容。

instanceof類型保護(hù)是通過構(gòu)造函數(shù)來細(xì)化類型的一種方式。 比如,我們借鑒一下之前字符串填充的例子:

interface Padder {

? ? getPaddingString(): string

}

class SpaceRepeatingPadder implements Padder {

? ? constructor(private numSpaces: number) { }

? ? getPaddingString() {

? ? ? ? return Array(this.numSpaces + 1).join(" ");

? ? }

}

class StringPadder implements Padder {

? ? constructor(private value: string) { }

? ? getPaddingString() {

? ? ? ? return this.value;

? ? }

}

function getRandomPadder() {

? ? return Math.random() < 0.5 ?

? ? ? ? new SpaceRepeatingPadder(4) :

? ? ? ? new StringPadder("? ");

}

// 類型為SpaceRepeatingPadder | StringPadder

let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {

? ? padder; // 類型細(xì)化為'SpaceRepeatingPadder'

}

if (padder instanceof StringPadder) {

? ? padder; // 類型細(xì)化為'StringPadder'

}

instanceof的右側(cè)要求是一個(gè)構(gòu)造函數(shù),TypeScript將細(xì)化為:

此構(gòu)造函數(shù)的 prototype屬性的類型,如果它的類型不為 any的話

構(gòu)造簽名所返回的類型的聯(lián)合

以此順序。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容