TypeScript - 升級篇

一、對象的類型——接口

在 TypeScript 中,我們使用接口(Interfaces)來定義對象的類型

1、簡單例子

interface Person {
    name: string;
    age: number;
};
let tom: Person = {
    name: 'terminals ',
    age: 25
};

上面的例子中,我們定義了一個接口 Person,接着定義了一個變量 tom,它的類型是 Person。這樣,我們就約束了 tom 的形狀必須和接口 Person 一致。

接口一般首字母大寫

注意: 定義的變量比接口少了一些屬性是不允許的:

interface Person {
    name: string;
    age: number;
}
let tom: Person = {
    name: 'terminals'
};

多一些屬性也是不允許的:

interface Person {
    name: string;
    age: number;
}
let tom: Person = {
    name: terminals,
    age: 25,
    gender: 'male'
};

可見,賦值的時候,變量的形狀必須和接口的形狀保持一致。

2、可選屬性

有時我們希望不要完全匹配一個形狀,那麼可以用可選屬性:

可選屬性的含義是該屬性可以不存在。

interface Person {
    name: string;
    age?: number;
}
let tom: Person = {
    name: 'terminals'
};
interface Person {
    name: string;
    age?: number;
}
let tom: Person = {
    name: ' terminals ',
    age: 25
};
這時仍然不允許添加未定義的屬性:
interface Person {
    name: string;
    age?: number;
}
let tom: Person = {
    name: 'terminals',
    age: 25,
    gender: 'male'
};

3、任意屬性

有時候我們希望一個接口允許有任意的屬性,可以使用如下方式:

interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}
let tom: Person = {
    name: 'terminals',
    gender: 'male'
};

使用 [propName: string] 定義了任意屬性取 string 類型的值。

需要注意的是,一旦定義了任意屬性,那麼確定屬性和可選屬性的類型都必須是它的類型的子集:

interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}
let tom: Person = {
    name: 'terminals',
    age: 25,
    gender: 'male'
};

任意屬性的值允許是 string,但是可選屬性 age 的值卻是 number,number 不是 string 的子屬性,所以報錯了。

4、只讀屬性

有時候我們希望對象中的一些字段只能在創建的時候被賦值,那麼可以用 readonly 定義只讀屬性:

interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}
let tom: Person = {
    id: 89757,
    name: 'Terminals',
    gender: 'male'
};

tom.id = 9527;

上例中,使用 readonly 定義的屬性 id 初始化後,又被賦值了,所以報錯。

interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}
let tom: Person = {
    name: 'Terminals',
    gender: 'male'
};
tom.id = 89757;

上例中,報錯信息有兩處,第一處是在對 tom 進行賦值的時候,沒有給 id 賦值。

第二處是在給 tom.id 賦值的時候,由於它是隻讀屬性,所以報錯了。

二、數組的類型

數組類型有多種定義方式

1、「類型 + 方括號」表示法

最簡單的方法是使用「類型 + 方括號」來表示數組:

let fibonacci: number[] = [1, 1, 2, 3, 5];

數組的項中不允許出現其他的類型:

let fibonacci: number[] = [1, '1', 2, 3, 5];

數組的一些方法的參數也會根據數組在定義時約定的類型進行限制:​​​​​​​

let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push('8');

上例中,push 方法只允許傳入 number 類型的參數,但是卻傳了一個 string 類型的參數,所以報錯了。

2、數組泛型

也可以使用數組泛型(Array Generic) Array<elemType> 來表示數組:

let fibonacci: Array<number> = [1, 1, 2, 3, 5];

3、用接口表示數組

接口也可以用來描述數組:​​​​​​​

interface NumberArray {
    //前面是索引值,後面是返回的 number類型的結果
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

NumberArray 表示:只要 index 的類型是 number,那麼值的類型必須是 number。

4、any 在數組中的應用

一個比較常見的做法是,用 any 表示數組中允許出現任意類型:

let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }];

5、類數組

類數組(Array-like Object)不是數組類型,比如 arguments:​​​​​​​

function sum() {
    let args: number[] = arguments;
}

報錯

事實上常見的類數組都有自己的接口定義,如 IArguments, NodeList, HTMLCollection 等:​​​​​​​

function sum() {
    let args: IArguments = arguments;
}

三、函數的類型

1、函數聲明

在 JavaScript 中,有兩種常見的定義函數的方式——函數聲明(Function Declaration)和函數表達式(Function Expression)

// 函數聲明(Function Declaration)​​​​​​​

function sum(x, y) {
    return x + y;
}

// 函數表達式(Function Expression)​​​​​​​

let mySum = function (x, y) {
    return x + y;
};

一個函數有輸入和輸出,要在 TypeScript 中對其進行約束,需要把輸入和輸出都考慮到,其中函數聲明的類型定義較簡單:​​​​​​​

function sum(x: number, y: number): number {
    return x + y;
}

注意,輸入多餘的(或者少於要求的)參數,是不被允許的:​​​​​​​

function sum(x: number, y: number): number {
    return x + y;
}
sum(1, 2, 3);
sum(1)

2、函數表達式

如果要我們現在寫一個對函數表達式(Function Expression)的定義,可能會寫成這樣:​​​​​​​

let mySum = function (x: number, y: number): number {
    return x + y;
};

這是可以通過編譯的,不過事實上,上面的代碼只對等號右側的匿名函數進行了類型定義,而等號左邊的 mySum,是通過賦值操作進行類型推論而推斷出來的。如果需要我們手動給 mySum 添加類型,則應該是這樣:​​​​​​​

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};

注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>。

在 TypeScript 的類型定義中,=> 用來表示函數的定義,左邊是輸入類型,需要用括號括起來,右邊是輸出類型。

3、可選參數

前面提到,輸入多餘的(或者少於要求的)參數,是不允許的。那麼如何定義可選的參數呢?

與接口中的可選屬性類似,我們用 ? 表示可選的參數:​​​​​​​

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

 

需要注意的是,可選參數必須接在必需參數後面。換句話說,可選參數後面不允許再出現必須參數了:A required parameter cannot follow an optional parameter.​​​​​​​

function buildName(firstName?: string, lastName: string) {
    if (firstName) {
        return firstName + ' ' + lastName;
    } else {
        return lastName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName(undefined, 'Tom');

4、參數默認值

在 ES6 中,我們允許給函數的參數添加默認值,TypeScript 會將添加了默認值的參數識別爲可選參數:​​​​​​​

function buildName(firstName: string, lastName: string = 'Cat') {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

此時就不受「可選參數必須接在必需參數後面」的限制了:​​​​​​​

function buildName(firstName: string = 'Tom', lastName: string) {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');

5、剩餘參數

ES6 中,可以使用 ...rest 的方式獲取函數中的剩餘參數(rest 參數):​​​​​​​

function push(array, ...items) {
    items.forEach(function(item) {
        array.push(item);
    });
}
let a = [];
push(a, 1, 2, 3);
事實上,items 是一個數組。所以我們可以用數組的類型來定義它:
function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}
let a = [];
push(a, 1, 2, 3);

6、重載

重載允許一個函數接受不同數量或類型的參數時,作出不同的處理。

利用聯合類型,我們可以這麼實現:​​​​​​​

function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

然而這樣有一個缺點,就是不能夠精確的表達,輸入爲數字的時候,輸出也應該爲數字,輸入爲字符串的時候,輸出也應該爲字符串。

這時,我們可以使用重載定義多個 reverse 的函數類型:​​​​​​​

// 上邊是聲明
function reverse(x: number): number;
function reverse(x: string): string;
// 下邊是實現
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

上例中,我們重複定義了多次函數x reverse,前幾次都是函數定義,最後一次是函數實現。在編輯器的代碼提示中,可以正確的看到前兩個提示。

注意,TypeScript 會優先從最前面的函數定義開始匹配,所以多個函數定義如果有包含關係,需要優先把精確的定義寫在前面。

四、類型斷言

類型斷言(Type Assertion)可以用來手動指定一個值的類型

語法:<類型>值 或者 值 as 類型

在 tsx 語法(React 的 jsx 語法的 ts 版)中必須用後一種。

例子:將一個聯合類型的變量指定爲一個更加具體的類型

當 TypeScript 不確定一個聯合類型的變量到底是哪個類型的時候,我們只能訪問此聯合類型的所有類型裏共有的屬性或方法:​​​​​​​

function getLength(something: string | number): number {
    return something.length;
}

上例中,獲取 something.length 的時候會報錯。

此時可以使用類型斷言,將 something 斷言成 string:

類型斷言的用法如上,在需要斷言的變量前加上 <Type> 即可。​​​​​​​

function getLength(something: string | number): number {
    if ((<string>something).length) {
        return (<string>something).length;
    } else {
        return something.toString().length;
    }
}

類型斷言不是類型轉換,斷言成一個聯合類型中不存在的類型是不允許的:​​​​​​​

function toBoolean(something: string | number): boolean {
    return <boolean>something;
}

 

交流

可添加qq羣共同進階學習: 進軍全棧工程師疑難解  羣號:   856402057

我是老禮,公衆號「進軍全棧攻城獅」作者 ,對前端技術保持學習愛好者。我會經常分享自己所學所看的乾貨,在進階的路上,共勉!

                                                    

 

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