TypeScript 語法學習(二)

介紹

從 ES6 開始,JS 程序員將能夠使用基於類的面向對象的方式。 使用TypeScript,我們允許開發者現在就使用這些特性,並且編譯後的 JS 可以在所有主流瀏覽器和平臺上運行,而不需要等到下個 JS 版本。

使用

class Greeter {
    greeting: string
    constructor (message: string) {
        this.greeting = message;
    }
    greet(){
        return "Hello, " + this.greeting;
    }
    
}
let greeter = new Greeter('world')

console.log(greeter.greet());

在 TS 中可以約束參入的值

繼承

和 ES6 的語法一樣,如果你不會 ES6 的語法,最好還是看一下阮老師寫的:https://es6.ruanyifeng.com/#docs/class

class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 45) {
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);

定義一個可以繼承方法和對象的類。

修飾符

默認爲公共(public)

我們之前在屬性和方法前沒有寫任何東西,就意味着默認是 public,就像下面一樣

public 意味着 我們可以自由的訪問程序裏定義的成員

class Animal {
    public name: string;
    public constructor(theName: string) { this.name = theName; }
    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

私有(private)

private 意味着 它就不能在聲明它的類的外部訪問,是可以繼承 ,例如:

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

new Animal("Cat").name; // 錯誤: 'name' 是私有的.

當我們比較兩種不同的類型時,並不在乎它們從何處而來,如果所有成員的類型都是兼容的,我們就認爲它們的類型是兼容的。然而,當我們比較帶有 privateprotected成員的類型的時候,情況就不同了。如果其中一個類型裏包含一個 private成員,那麼只有當另外一個類型中也存在這樣一個 private成員, 並且它們都是來自同一處聲明時,我們才認爲這兩個類型是兼容的。 對於 protected成員也使用這個規則。

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

class Rhino extends Animal {
    constructor() { super("Rhino"); }
}

class Employee {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");

animal = rhino;
animal = employee; // 錯誤: Animal 與 Employee 不兼容.

在這裏爲什麼Animal 與 Employee 不兼容,而 Animal 和 Rhino 就兼容呢?雖然 Animal 和 Employee 一樣,僅僅 名字不同。就是因爲 private,上面說的比較規則。因爲 Animal 和 Rhino 是繼承關係,屬於同一聲明。

protected

protected修飾符與 private修飾符的行爲很相似,但有一點不同, protected成員在派生(繼承)類中仍然可以訪問

class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }
}

class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name)
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 錯誤

注意,我們不能在 Person類外使用 name,但是我們仍然可以通過 Employee類的實例方法訪問,因爲 Employee是由 Person派生而來的。

構造函數也可以被標記成 protected。 這意味着這個類不能在包含它的類外被實例化,但是能被繼承。比如,

class Person {
    protected name: string;
    protected constructor(theName: string) { this.name = theName; }
}

// Employee 能夠繼承 Person
class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name);
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 錯誤: 'Person' 的構造函數是被保護的.

static

之前的代碼都是在討論實例的,staic 修飾符是 存在於類本身上面而不是類的實例上,被標記的靜態屬性或方法都只可以在類上訪問,而在類創建的實例上是不可以進行訪問的。

class Grid {
    static origin = {x: 0, y: 0};
    static calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        let xDist = (point.x - Grid.origin.x);
        let yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    scale:number
    constructor (scale: number) {
        this.scale = scale;
     }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale
console.log(grid1.scale);
grid1.origin    // 訪問不了
Grid.origin     // 可以訪問

readonly

你可以使用 readonly關鍵字將屬性設置爲只讀的。 只讀屬性必須在聲明時或構造函數裏被初始化。

class Octopus {
    readonly name: string;
    readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 錯誤! name 是隻讀的.

其實我們可以在構造函數中使用 readonly ,這個叫做 參數屬性

class Octopus {
    readonly numberOfLegs: number = 8;
    constructor(readonly name: string) {
    }
}

最好還是不要使用 參數屬性,使用第一種方法比較好(清晰明瞭)

存取器

其實就是 ES5中的 getters/setters

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        this._fullName = newName;
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    console.log(employee.fullName);
}

注意 typescript 默認輸出 ES3 版本的代碼,getters/setters 是 ES5 的語法,如果想要使用就必須把輸出帶包調整到 ES5

抽象類

抽象類做爲其它派生類的基類使用。 它們一般不會直接被實例化。 不同於接口,抽象類可以包含成員的實現細節。 abstract關鍵字是用於定義抽象類和在抽象類內部定義抽象方法。

abstract class Department {
    name:string
    constructor(name: string) {
        this.name = name
    }
    printName(): void {
        console.log('Department name: ' + this.name);
    }

    abstract printMeeting(): void; // 必須在派生類中實現
}

class AccountingDepartment extends Department {

    constructor() {
        super('Accounting and Auditing'); // 在派生類的構造函數中必須調用 super()
    }

    printMeeting(): void {
        console.log('The Accounting Department meets each Monday at 10am.');
    }

    generateReports(): void {
        console.log('Generating accounting reports...');
    }
}

let department: Department; // 允許創建一個對抽象類型的引用
department = new Department(); // 錯誤: 不能創建一個抽象類的實例
department = new AccountingDepartment(); // 允許對一個抽象子類進行實例化和賦值
department.printName();
department.printMeeting();
department.generateReports(); // 錯誤: 方法在聲明的抽象類中不存在

函數

其實前面我也提到過一些函數,我認爲這個沒什麼好說的,如果你還不瞭解,可以看一下:https://www.tslang.cn/docs/handbook/functions.html

值得說一下的是 重載,其實這也沒什麼好說的,這個是 JS 函數自代的特性,下面是 JS 處理不同參數的重載

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

TS 的重載(java,c#類似的重載)

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

泛型

在像C#和Java這樣的語言中,可以使用泛型來創建可重用的組件,一個組件可以支持多種類型的數據。 這樣用戶就可以根據自己的數據類型來使用組件。

下面我們實現一個例子,你傳入什麼類型的數據,就返回什麼類型的數據

不用泛型的話,這個函數可能是下面這樣:

function identity(arg: number): number {
    return arg;
}

或者,我們使用any類型來定義函數:

function identity(arg: any): any {
    return arg;
}

使用any類型會導致這個函數可以接收任何類型的arg參數,這樣就丟失了一些信息:傳入的類型與返回的類型應該是相同的。如果我們傳入一個數字,我們只知道任何類型的值都有可能被返回。下面的函數就會返回字符串,不符合我們的要求。

function identity(arg: any): any {
    return arg + 'sd';
}

因此,我們需要一種方法使返回值的類型與傳入參數的類型是相同的。 這裏,我們使用了 類型變量,它是一種特殊的變量,只用於表示類型而不是值。下面我們定義了一個泛型函數,可以根據我們傳入的類型來返回數據

function identity<T>(arg: T): T {
    return arg;
}
let output = identity<string>('hello');	//第一種
let output = identity('hello');			//第二種 (推薦)
// 其實第一種和第二種方法一樣,第二種只不過是利用了類型推斷
console.log(output);

泛型變量

使用泛型創建像identity這樣的泛型函數時,編譯器要求你在函數體必須正確的使用這個通用的類型。 換句話說,你必須把這些參數當做是任意或所有類型。

看下之前identity例子:

function identity<T>(arg: T): T {
    return arg;
}

但是如果我們想同時打印出arg的長度。 我們很可能會這樣做:

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

如果這麼做,編譯器會報錯說我們使用了arg.length屬性,但是沒有地方指明arg具有這個屬性。 記住,這些類型變量代表的是任意類型,所以使用這個函數的人可能傳入的是個數字,而數字是沒有 .length屬性的。

現在假設我們想操作T類型的數組而不直接是T。由於我們操作的是數組,所以.length屬性是應該存在的。 我們可以像創建其它數組一樣創建這個數組:

function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

你可以這樣理解loggingIdentity的類型:泛型函數loggingIdentity,接收類型參數T和參數arg,它是個元素類型是T的數組,並返回元素類型是T的數組。 如果我們傳入數字數組,將返回一個數字數組,因爲此時 T的的類型爲number。 這可以讓我們把泛型變量T當做類型的一部分使用,而不是整個類型,增加了靈活性。

我們也可以這樣實現上面的例子:

function loggingIdentity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

泛型類型

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <T>(arg: T) => T = identity;

我們也可以使用不同的泛型參數名,只要在數量上和使用方式上能對應上就可以。

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <U>(arg: U) => U = identity;

我們還可以使用帶有調用簽名的對象字面量來定義泛型函數:

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: {<T>(arg: T): T} = identity;

這引導我們去寫第一個泛型接口了。 我們把上面例子裏的對象字面量拿出來做爲一個接口:

interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn = identity;

一個相似的例子,我們可能想把泛型參數當作整個接口的一個參數。 這樣我們就能清楚的知道使用的具體是哪個泛型類型(比如: Dictionary<string>而不只是Dictionary)。 這樣接口裏的其它成員也能知道這個參數的類型了。

interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;
// 推薦這種方式

注意,我們的示例做了少許改動。 不再描述泛型函數,而是把非泛型函數簽名作爲泛型類型一部分。 當我們使用 GenericIdentityFn的時候,還得傳入一個類型參數來指定泛型類型(這裏是:number),鎖定了之後代碼裏使用的類型。 對於描述哪部分類型屬於泛型部分來說,理解何時把參數放在調用簽名裏和何時放在接口上是很有幫助的。

除了泛型接口,我們還可以創建泛型類。 注意,無法創建泛型枚舉和泛型命名空間。

泛型類

泛型類看上去與泛型接口差不多。 泛型類使用( <>)括起泛型類型,跟在類名後面。

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

GenericNumber類的使用是十分直觀的,並且你可能已經注意到了,沒有什麼去限制它只能使用number類型。 也可以使用字符串或其它更復雜的類型。

let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };

console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));

與接口一樣,直接把泛型類型放在類後面,可以幫助我們確認類的所有屬性都在使用相同的類型。

我們在類那節說過,類有兩部分:靜態部分和實例部分。 泛型類指的是實例部分的類型,所以類的靜態屬性不能使用這個泛型類型。

泛型約束

你應該會記得之前的一個例子,我們有時候想操作某類型的一組值,並且我們知道這組值具有什麼樣的屬性。 在 loggingIdentity例子中,我們想訪問arglength屬性,但是編譯器並不能證明每種類型都有length屬性,所以就報錯了。

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

相比於操作any所有類型,我們想要限制函數去處理任意帶有.length屬性的所有類型。 只要傳入的類型有這個屬性,我們就允許,就是說至少包含這一屬性。 爲此,我們需要列出對於T的約束要求。

爲此,我們定義一個接口來描述約束條件。 創建一個包含 .length屬性的接口,使用這個接口和extends關鍵字來實現約束:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

現在這個泛型函數被定義了約束,因此它不再是適用於任意類型:

loggingIdentity(3);  // Error, number doesn't have a .length property

我們需要傳入符合約束類型的值,必須包含必須的屬性:

loggingIdentity({length: 10, value: 3});

在泛型約束中使用類型參數

你可以聲明一個類型參數,且它被另一個類型參數所約束。 比如,現在我們想要用屬性名從對象裏獲取這個屬性。 並且我們想要確保這個屬性存在於對象 obj上,因此我們需要在這兩個類型之間使用約束。

function getProperty<T,K extends keyof T>(obj: T,key: K){
    return obj[key];
}

let x = {one:1,two:2,three:3};

console.log(getProperty(x,'one'));
console.log(getProperty(x,'four'));	//  Argument of type '"four"' is not assignable to parameter of type '"one" | "two" | "three"'

說明一下 keyof 是返回對象的所有索引構成的聯合類型

類型推斷

TypeScript裏,在有些沒有明確指出類型的地方,類型推論會幫助提供類型。如下面的例子

let x = 3;

變量x的類型被推斷爲數字。 這種推斷髮生在初始化變量和成員,設置默認參數值和決定函數返回值時。

大多數情況下,類型推論是直截了當地。 後面的小節,我們會瀏覽類型推論時的細微差別。

最佳通用類型

當需要從幾個表達式中推斷類型時候,會使用這些表達式的類型來推斷出一個最合適的通用類型。例如,

let x = [0, 1, null];

爲了推斷x的類型,我們必須考慮所有元素的類型。 這裏有兩種選擇: numbernull。 計算通用類型算法會考慮所有的候選類型,並給出一個兼容所有候選類型的類型。

由於最終的通用類型取自候選類型,有些時候候選類型共享相同的通用類型,但是卻沒有一個類型能做爲所有候選類型的類型。例如:

let zoo = [new Rhino(), new Elephant(), new Snake()];

這裏,我們想讓zoo被推斷爲Animal[]類型,但是這個數組裏沒有對象是Animal類型的,因此不能推斷出這個結果。 爲了更正,當候選類型不能使用的時候我們需要明確的指出類型:

let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

如果沒有找到最佳通用類型的話,類型推斷的結果爲聯合數組類型,(Rhino | Elephant | Snake)[]

上下文類型

TypeScript類型推論也可能按照相反的方向進行。 這被叫做“按上下文歸類”。按上下文歸類會發生在表達式的類型與所處的位置相關時。比如:

window.onmousedown = function(mouseEvent) {
    console.log(mouseEvent.button);  //<- Error
};

這個例子會得到一個類型錯誤,TypeScript類型檢查器使用Window.onmousedown函數的類型來推斷右邊函數表達式的類型。 因此,就能推斷出 mouseEvent參數的類型了。 如果函數表達式不是在上下文類型的位置, mouseEvent參數的類型需要指定爲any,這樣也不會報錯了。

如果上下文類型表達式包含了明確的類型信息,上下文的類型被忽略。 重寫上面的例子:

window.onmousedown = function(mouseEvent: any) {
    console.log(mouseEvent.button);  //<- Now, no error is given
};

這個函數表達式有明確的參數類型註解,上下文類型被忽略。 這樣的話就不報錯了,因爲這裏不會使用到上下文類型。

上下文歸類會在很多情況下使用到。 通常包含函數的參數,賦值表達式的右邊,類型斷言,對象成員和數組字面量和返回值語句。 上下文類型也會做爲最佳通用類型的候選類型。比如:

function createZoo(): Animal[] {
    return [new Rhino(), new Elephant(), new Snake()];
}

這個例子裏,最佳通用類型有4個候選者:AnimalRhinoElephantSnake。 當然, Animal會被做爲最佳通用類型。

高級類型

交叉類型

交叉類型是將多個類型合併爲一個類型。 這讓我們可以把現有的多種類型疊加到一起成爲一種類型,它包含了所需的所有類型的特性。 例如, Person & Serializable & Loggable同時是 Person Serializable Loggable。 就是說這個類型的對象同時擁有了這三種類型的成員。

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};	// {} as T & U
    for (let id in first) {
        result[id] = first[id] as any;
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            result[id] = second[id] as any;
        }
    }
    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();

聯合類型

偶爾你會遇到這種情況,一個代碼庫希望傳入 numberstring類型的參數,你就可以使用聯合類型

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

padding 就既可以是字符串或數字類型

如果是你自己定義的類,接口,泛型也是可以的

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

類型保護和區分類型

還是上一個例子,但當我們想確切地瞭解是否爲 Fish時怎麼辦? JavaScript裏常用來區分2個可能值的方法是檢查成員是否存在。

當然我們可以通過 if 的方式確認,類型斷言 也是可以的。

這裏介紹類型保護,下面一個例子,判斷到底是否是Fish 接口?

function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}

類型保護 通過謂詞爲 parameterName is Type這種形式, parameterName必須是來自於當前函數簽名裏的一個參數名。

還有一種 typeof 提供的類型保護

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}'.`);
}

現在我們不必將 typeof x === "number"抽象成一個函數,因爲TypeScript可以將它識別爲一個類型保護。 也就是說我們可以直接在代碼裏檢查類型了。

我們還可以使用instanceof來進行類型保護

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; // 類型細化爲'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
    padder; // 類型細化爲'StringPadder'
}

instanceof的右側要求是一個構造函數,TypeScript將細化爲:

  1. 此構造函數的 prototype屬性的類型,如果它的類型不爲 any的話
  2. 構造簽名所返回的類型的聯合

以此順序。

手動取消類型斷言

如果編譯器不能夠去除 nullundefined,你可以使用類型斷言手動去除。 語法是添加 !後綴: identifier!identifier的類型裏去除了 nullundefined

function broken(name: string | null): string {
  function postfix(epithet: string) {
    return name.charAt(0) + '.  the ' + epithet; // error, 'name' is possibly null
  }
  name = name || "Bob";
  return postfix("great");
}

function fixed(name: string | null): string {
  function postfix(epithet: string) {
    return name!.charAt(0) + '.  the ' + epithet; // ok
  }
  name = name || "Bob";
  return postfix("great");
}

字符串字面量

字符串字面量類型允許你指定字符串必須的固定值。 在實際應用中,字符串字面量類型可以與聯合類型,類型保護和類型別名很好的配合。 通過結合使用這些特性,你可以實現類似枚舉類型的字符串。

type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
    animate(dx: number, dy: number, easing: Easing) {
        if (easing === "ease-in") {
            // ...
        }
        else if (easing === "ease-out") {
        }
        else if (easing === "ease-in-out") {
        }
        else {
            // error! should not pass null or undefined.
        }
    }
}

let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here

通過使用 type 來實現

你只能從三種允許的字符中選擇其一來做爲參數傳遞,傳入其它值則會產生錯誤。

當然也可以使用數字字面量

其實還有其他的一些特性,我只總結了一些常用的東西,剩下的就需要去看官方的文檔了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章