TypeScript入門學(xué)習(xí)

一、什么是 TypeScript

TypeScript 是 JavaScript 的一個(gè)超集,主要提供了類型系統(tǒng)對(duì) ES6 的支持,它由 Microsoft 開發(fā),代碼開源于 GitHub 上。

二、為什么選擇 TypeScript

1.TypeScript 增加了代碼的可讀性和可維護(hù)性

  • 類型系統(tǒng)實(shí)際上是最好的文檔,大部分的函數(shù)看看類型的定義就可以知道如何使用了
  • 可以在編譯階段就發(fā)現(xiàn)大部分錯(cuò)誤,這總比在運(yùn)行時(shí)候出錯(cuò)好
  • 增強(qiáng)了編輯器和 IDE 的功能,包括代碼補(bǔ)全、接口提示、跳轉(zhuǎn)到定義、重構(gòu)等

2.TypeScript 非常包容

  • TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名為 .ts 即可
  • 即使不顯式的定義類型,也能夠自動(dòng)做出類型推論
  • 可以定義從簡(jiǎn)單到復(fù)雜的幾乎一切類型
  • 即使 TypeScript 編譯報(bào)錯(cuò),也可以生成 JavaScript 文件
  • 兼容第三方庫(kù),即使第三方庫(kù)不是用 TypeScript 寫的,也可以編寫單獨(dú)的類型文件供 TypeScript 讀取

3.TypeScript 擁有活躍的社區(qū)

  • 大部分第三方庫(kù)都有提供給 TypeScript 的類型定義文件
  • Google 開發(fā)的 Angular2 就是使用 TypeScript 編寫的
  • TypeScript 擁抱了 ES6 規(guī)范,也支持部分 ESNext 草案的規(guī)范

4.TypeScript 的缺點(diǎn)

  • 有一定的學(xué)習(xí)成本,需要理解接口(Interfaces)、泛型(Generics)、類(Classes)、枚舉類型(Enums)等前端工程師可能不是很熟悉的概念
  • 短期可能會(huì)增加一些開發(fā)成本,畢竟要多寫一些類型的定義,不過對(duì)于一個(gè)需要長(zhǎng)期維護(hù)的項(xiàng)目,TypeScript 能夠減少其維護(hù)成本
  • 集成到構(gòu)建流程需要一些工作量
  • 可能和一些庫(kù)結(jié)合的不是很完美

三、安裝 TypeScript

TypeScript 的命令行工具安裝方法如下:

npm install -g typescript

以上命令會(huì)在全局環(huán)境下安裝 tsc 命令,安裝完成之后,我們就可以在任何地方執(zhí)行 tsc 命令了。
編譯一個(gè) TypeScript 文件很簡(jiǎn)單:

tsc hello.ts

我們約定使用 TypeScript 編寫的文件以 .ts 為后綴,用 TypeScript 編寫 React 時(shí),以 .tsx 為后綴。

四、TypeScript基礎(chǔ)

1.定義原始數(shù)據(jù)類型

JavaScript 的類型分為兩種:原始數(shù)據(jù)類型(Primitive data types)和對(duì)象類型(Object types)。

原始數(shù)據(jù)類型包括:布爾值數(shù)值、字符串、null、undefined 以及 ES6 中的新類型 Symbol。

let isDone: boolean = false;  //布爾值
let decLiteral: number = 6;   //數(shù)值
let myName: string = 'Tom';  //字符串
//JavaScript 沒有空值(Void)的概念,在 TypeScript 中,可以用 void 表示沒有任何返回值的函數(shù):
function alertName(): void {
    alert('My name is Tom');
}
//undefined 類型的變量只能被賦值為 undefined,null 類型的變量只能被賦值為 null。
let u: undefined = undefined;
let n: null = null;

TypeScript 中,使用 : 指定變量的類型。TypeScript 只會(huì)進(jìn)行靜態(tài)檢查,如果發(fā)現(xiàn)有錯(cuò)誤,編譯的時(shí)候就會(huì)報(bào)錯(cuò)。TypeScript 編譯的時(shí)候即使報(bào)錯(cuò)了,還是會(huì)生成編譯結(jié)果,我們?nèi)匀豢梢允褂眠@個(gè)編譯之后的文件。

2.定義任意值類型

任意值(Any)用來表示允許賦值為任意類型。

如果是一個(gè)普通類型,在賦值過程中改變類型是不被允許的:

let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

但如果是 any 類型,則允許被賦值為任意類型。在任意值上訪問任何屬性都是允許的,也允許調(diào)用任何方法

let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
let anyThing: any = 'hello';
console.log(anyThing.myName.firstName);
anyThing.setName('Jerry').sayHello();

可以認(rèn)為,聲明一個(gè)變量為任意值之后,對(duì)它的任何操作,返回的內(nèi)容的類型都是任意值。

變量如果在聲明的時(shí)候,未指定其類型,那么它會(huì)被識(shí)別為任意值類型:

let something;
something = 'seven';
something = 7;

something.setName('Tom');

3.類型推論

如果沒有明確的指定類型,那么 TypeScript 會(huì)依照類型推論(Type Inference)的規(guī)則推斷出一個(gè)類型。

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

如果定義的時(shí)候沒有賦值,不管之后有沒有賦值,都會(huì)被推斷成 any 類型而完全不被類型檢查:

let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

4.聯(lián)合類型

聯(lián)合類型(Union Types)表示取值可以為多種類型中的一種。聯(lián)合類型使用 | 分隔每個(gè)類型。

let myFavoriteNumber: string | number;
myFavoriteNumber = true;

// index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
//   Type 'boolean' is not assignable to type 'number'.
//這里的 let myFavoriteNumber: string | number 的含義是,允許 myFavoriteNumber 的類型是 string 或者 number,但是不能是其他類型。

當(dāng) TypeScript 不確定一個(gè)聯(lián)合類型的變量到底是哪個(gè)類型的時(shí)候,我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法:

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

// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.

上例中,length 不是 string 和 number 的共有屬性,所以會(huì)報(bào)錯(cuò)。

5.接口(Interfaces)

TypeScript 中的接口是一個(gè)非常靈活的概念,它可用于對(duì)類的一部分行為進(jìn)行抽象以外,也常用于對(duì)「對(duì)象的形狀(Shape)」進(jìn)行描述。

  • 1?? 對(duì) 「對(duì)象的形狀(Shape)」進(jìn)行描述
interface Person {
    name: string;
    age: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25
};

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

接口一般首字母大寫,定義的變量比接口少了一些屬性是不允許的,多一些屬性也是不允許的。賦值的時(shí)候,變量的形狀必須和接口的形狀保持一致

可選屬性

有時(shí)我們希望不要完全匹配一個(gè)形狀,那么可以用 可選屬性

interface Person {
    name: string; 
    age?: number;
}

let tom: Person = {
    name: 'Tom'
};

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

任意屬性

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

interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

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

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

interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.

上例中,任意屬性的值允許是 string,但是可選屬性 age 的值卻是 number,number 不是 string 的子屬性,所以報(bào)錯(cuò)了。

只讀屬性

有時(shí)候我們希望對(duì)象中的一些字段只能在創(chuàng)建的時(shí)候被賦值,那么可以用 readonly 定義只讀屬性:

interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

tom.id = 9527;

// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

上例中,使用 readonly 定義的屬性 id 初始化后,又被賦值了,所以報(bào)錯(cuò)了。
注意,只讀的約束存在于第一次給對(duì)象賦值的時(shí)候,而不是第一次給只讀屬性賦值的時(shí)候

  • 2?? 對(duì)類的一部分行為進(jìn)行抽象
    實(shí)現(xiàn)(implements)是面向?qū)ο笾械囊粋€(gè)重要概念。一般來講,一個(gè)類只能繼承自另一個(gè)類,有時(shí)候不同類之間可以有一些共有的特性,這時(shí)候就可以把特性提取成接口(interfaces),用 implements 關(guān)鍵字來實(shí)現(xiàn)。這個(gè)特性大大提高了面向?qū)ο蟮撵`活性。

舉例來說,門是一個(gè)類,防盜門是門的子類。如果防盜門有一個(gè)報(bào)警器的功能,我們可以簡(jiǎn)單的給防盜門添加一個(gè)報(bào)警方法。這時(shí)候如果有另一個(gè)類,車,也有報(bào)警器的功能,就可以考慮把報(bào)警器提取出來,作為一個(gè)接口,防盜門和車都去實(shí)現(xiàn)它:

interface Alarm {
    alert();
}

class Door {
}

class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}

class Car implements Alarm {
    alert() {
        console.log('Car alert');
    }
}
interface Alarm {
    fn();
}

class Door {
}

class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}

class Car implements Alarm {
    alert() {
        console.log('Car alert');
    }
}
//錯(cuò)誤信息如下:
index.ts:22:7 - error TS2420: Class 'SecurityDoor' incorrectly implements interface 'Alarm'.
  Property 'fn' is missing in type 'SecurityDoor' but required in type 'Alarm'.

22 class SecurityDoor extends Door implements Alarm {
         ~~~~~~~~~~~~

  index.ts:16:5
    16     fn();
           ~~~~~
    'fn' is declared here.

index.ts:28:7 - error TS2420: Class 'Car' incorrectly implements interface 'Alarm'.
  Property 'fn' is missing in type 'Car' but required in type 'Alarm'.

28 class Car implements Alarm {
         ~~~

  index.ts:16:5
    16     fn();
           ~~~~~
    'fn' is declared here.


Found 2 errors.

一個(gè)類可以實(shí)現(xiàn)多個(gè)接口:

interface Alarm {
    alert();
}

interface Light {
    lightOn();
    lightOff();
}

class Car implements Alarm, Light {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

上例中,Car 實(shí)現(xiàn)了 Alarm 和 Light 接口,既能報(bào)警,也能開關(guān)車燈。

接口繼承接口

接口與接口之間可以是繼承關(guān)系:

interface Alarm {
    alert();
}

interface LightableAlarm extends Alarm {
    lightOn();
    lightOff();
}
class Car implements LightableAlarm {
    alert() {
        console.log('SecurityDoor alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

上例中,我們使用 extends 使 LightableAlarm 繼承 Alarm。因此類car中需實(shí)現(xiàn)alert、lightOn、lightOff

接口繼承類

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3}

混合類型

可以使用接口的方式來定義一個(gè)函數(shù)需要符合的形狀:

interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}

有時(shí)候,一個(gè)函數(shù)還可以有自己的屬性和方法:

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

6.定義數(shù)組的類型

在 TypeScript 中,數(shù)組類型有多種定義方式,比較靈活。

  • 1?? 「類型 + 方括號(hào)」表示法
let fibonacci: number[] = [1, 1, 2, 3, 5];
let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }];
  • 2?? 數(shù)組泛型
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
  • 3?? 用接口表示數(shù)組
nterface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

7.函數(shù)的類型

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

// 函數(shù)聲明(Function Declaration)
function sum(x, y) {
    return x + y;
}

// 函數(shù)表達(dá)式(Function Expression)
let mySum = function (x, y) {
    return x + y;
};
  • 1?? 函數(shù)聲明
    輸入多余的(或者少于要求的)參數(shù),是不被允許的:
function sum(x: number, y: number): number {
    return x + y;
}
sum(1);

// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.

我們用 ? 表示可選的參數(shù)

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

需要注意的是,可選參數(shù)必須接在必需參數(shù)后面。換句話說,可選參數(shù)后面不允許再出現(xiàn)必須參數(shù)了

TypeScript 會(huì)將添加了默認(rèn)值的參數(shù)識(shí)別為可選參數(shù)

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

剩余參數(shù)

function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}

let a = [];
push(a, 1, 2, 3);

重載

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

上例中,我們重復(fù)定義了多次函數(shù) reverse,前幾次都是函數(shù)定義,最后一次是函數(shù)實(shí)現(xiàn),最終實(shí)現(xiàn)一個(gè)函數(shù) reverse,輸入數(shù)字 123 的時(shí)候,輸出反轉(zhuǎn)的數(shù)字 321,輸入字符串 'hello' 的時(shí)候,輸出反轉(zhuǎn)的字符串 'olleh'

  • 2?? 函數(shù)表達(dá)式
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};

注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>。在 TypeScript 的類型定義中,=> 用來表示函數(shù)的定義,左邊是輸入類型,需要用括號(hào)括起來,右邊是輸出類型。

  • 3?? 用接口定義函數(shù)的形狀
interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}

8.類型斷言

<類型>值 或 值 as 類型

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

有時(shí)候,我們確實(shí)需要在還不確定類型的時(shí)候就訪問其中一個(gè)類型的屬性或方法

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

// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.
// index.ts(3,26): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.

上例中,獲取 something.length 的時(shí)候會(huì)報(bào)錯(cuò)。此時(shí)可以使用類型斷言,將 something 斷言成 string:

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

或者

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

類型斷言不是類型轉(zhuǎn)換,斷言成一個(gè)聯(lián)合類型中不存在的類型是不允許的:

9.聲明文件

當(dāng)使用第三方庫(kù)時(shí),我們需要引用它的聲明文件,才能獲得對(duì)應(yīng)的代碼補(bǔ)全、接口提示等功能。
詳情該頁(yè)面深入學(xué)習(xí)。

五、TypeScript進(jìn)階

1.創(chuàng)建類型別名

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

2.字符串字面量類型

字符串字面量類型用來約束取值只能是某幾個(gè)字符串中的一個(gè)。

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
    // do something
}

handleEvent(document.getElementById('hello'), 'scroll');  // 沒問題
handleEvent(document.getElementById('world'), 'dbclick'); // 報(bào)錯(cuò),event 不能為 'dbclick'

// index.ts(7,47): error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.

3.元組

數(shù)組合并了相同類型的對(duì)象,而元組(Tuple)合并了不同類型的對(duì)象。

let xcatliu: [string, number] = ['Xcat Liu', 25];

當(dāng)添加越界的元素時(shí),它的類型會(huì)被限制為元組中每個(gè)類型的聯(lián)合類型:

let xcatliu: [string, number];
xcatliu = ['Xcat Liu', 25];
xcatliu.push('http://xcatliu.com/');
xcatliu.push(true);

// index.ts(4,14): error TS2345: Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
//   Type 'boolean' is not assignable to type 'number'.

4.枚舉

枚舉(Enum)類型用于取值被限定在一定范圍內(nèi)的場(chǎng)景,比如一周只能有七天,顏色限定為紅綠藍(lán)等。

enum Weekday {sun,mon,tue,wed,thu,fri,sat};
let day: Weekday = Weekday.sun

編譯后的js代碼:

var Weekday;
(function (Weekday) {
    Weekday[Weekday["sun"] = 0] = "sun";
    Weekday[Weekday["mon"] = 1] = "mon";
    Weekday[Weekday["tue"] = 2] = "tue";
    Weekday[Weekday["wed"] = 3] = "wed";
    Weekday[Weekday["thu"] = 4] = "thu";
    Weekday[Weekday["fri"] = 5] = "fri";
    Weekday[Weekday["sat"] = 6] = "sat";
})(Weekday || (Weekday = {}));
;
var day = Weekday.sun;

首個(gè)枚舉項(xiàng)的默認(rèn)值為 0。這點(diǎn)值得注意,在 JavaScript 中,0 是一個(gè)假值,那么就會(huì)出現(xiàn)一個(gè)問題,如果我想要判斷一個(gè)枚舉值是否存在怎么辦?我能直接 if (c) { ... } 這樣判斷嗎?顯然是不可靠的,請(qǐng)看下面代碼:

enum Color {Red, Green, Blue}

let c: Color;
if (!c) {
  console.log('Yes, I am not defined.');
}

c = Color.Red;
if (!c) {
  console.log('Oops, I have a valid value, but seems I am undefined!');
}

是不是,問題顯而易見,如果我們想要對(duì)此枚舉類型變量值進(jìn)行判斷,我們還得這么寫:typeof(c) !== 'undefined',非常繁瑣。而如果將首個(gè)枚舉項(xiàng)的值設(shè)置為 1,我們就可以放心地直接將變量放在條件判斷語(yǔ)句中進(jìn)行判斷了。

enum Color {Red =1, Green, Blue}

let c: Color;

5.類

TypeScript 可以使用三種訪問修飾符(Access Modifiers),分別是 public、private 和 protected

  • public 修飾的屬性或方法是公有的,可以在任何地方被訪問到,默認(rèn)所有的屬性和方法都是 public 的
  • private 修飾的屬性或方法是私有的,不能在聲明它的類的外部訪問
  • protected 修飾的屬性或方法是受保護(hù)的,它和 private 類似,區(qū)別是它在子類中也是允許被訪問的
class Animal {
    public name;
    public constructor(name) {
        this.name = name;
    }
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom

上面的例子中,name 被設(shè)置為了 public,所以直接訪問實(shí)例的 name 屬性是允許的。

很多時(shí)候,我們希望有的屬性是無法直接存取的,這時(shí)候就可以用 private 了:

class Animal {
    private name;
    public constructor(name) {
        this.name = name;
    }
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';

// index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
// index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.

使用 private 修飾的屬性或方法,在子類中也是不允許訪問的:

class Animal {
    private name;
    public constructor(name) {
        this.name = name;
    }
}

class Cat extends Animal {
    constructor(name) {
        super(name);
        console.log(this.name);
    }
}

// index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'.

而如果是用 protected 修飾,則允許在子類中訪問:

class Animal {
    protected name;
    public constructor(name) {
        this.name = name;
    }
}

class Cat extends Animal {
    constructor(name) {
        super(name);
        console.log(this.name);
    }
}

給類加上 TypeScript 的類型很簡(jiǎn)單,與接口類似:

class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    sayHi(): string {
      return `My name is ${this.name}`;
    }
}

let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack

6.泛型

泛型(Generics)是指在定義函數(shù)、接口或類的時(shí)候,不預(yù)先指定具體的類型,而在使用的時(shí)候再指定類型的一種特性。

首先,我們來實(shí)現(xiàn)一個(gè)函數(shù) createArray,它可以創(chuàng)建一個(gè)指定長(zhǎng)度的數(shù)組,同時(shí)將每一項(xiàng)都填充一個(gè)默認(rèn)值:

function createArray(length: number, value: any): Array<any> {
    let result = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

上例中,我們使用了之前提到過的數(shù)組泛型來定義返回值的類型。Array<any> 允許數(shù)組的每一項(xiàng)都為任意類型。但是我們預(yù)期的是,數(shù)組中每一項(xiàng)都應(yīng)該是輸入的 value 的類型。
這時(shí)候,泛型就派上用場(chǎng)了:

function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray<string>(3, 'x'); // ['x', 'x', 'x']

上例中,我們?cè)诤瘮?shù)名后添加了 <T>,其中 T 用來指代任意輸入的類型,在后面的輸入 value: T 和輸出 Array<T> 中即可使用了。

接著在調(diào)用的時(shí)候,可以指定它具體的類型為 string。當(dāng)然,也可以不手動(dòng)指定,而讓類型推論自動(dòng)推算出來:

定義泛型的時(shí)候,可以一次定義多個(gè)類型參數(shù):

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

上例中,我們定義了一個(gè) swap 函數(shù),用來交換輸入的元組。

泛型約束

在函數(shù)內(nèi)部使用泛型變量的時(shí)候,由于事先不知道它是哪種類型,所以不能隨意的操作它的屬性或方法:

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);
    return arg;
}

// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.

上例中,泛型 T 不一定包含屬性 length,所以編譯的時(shí)候報(bào)錯(cuò)了。

這時(shí),我們可以對(duì)泛型進(jìn)行約束,只允許這個(gè)函數(shù)傳入那些包含 length 屬性的變量。這就是泛型約束:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

上例中,我們使用了 extends 約束了泛型 T 必須符合接口 Lengthwise 的形狀,也就是必須包含 length 屬性。
此時(shí)如果調(diào)用 loggingIdentity 的時(shí)候,傳入的 arg 不包含 length,那么在編譯階段就會(huì)報(bào)錯(cuò)了

泛型也可以用于類的類型定義中:

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; };

泛型參數(shù)的默認(rèn)類型

function createArray<T = string>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

7.聲明合并

如果定義了兩個(gè)相同名字的函數(shù)、接口或類,那么它們會(huì)合并成一個(gè)類型:

  • 函數(shù)的合并
    我們可以使用重載定義多個(gè)函數(shù)類型:
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('');
    }
}
  • 接口的合并
    合并的屬性的類型必須是唯一的
interface Alarm {
    price: number;
}
interface Alarm {
    price: string;  // 類型不一致,會(huì)報(bào)錯(cuò)
    weight: number;
}

// index.ts(5,3): error TS2403: Subsequent variable declarations must have the same type.  Variable 'price' must be of type 'number', but here has type 'string'.

接口中方法的合并,與函數(shù)的合并一樣:

interface Alarm {
    price: number;
    alert(s: string): string;
}
interface Alarm {
    weight: number;
    alert(s: string, n: number): string;
}

相當(dāng)于:

interface Alarm {
    price: number;
    weight: number;
    alert(s: string): string;
    alert(s: string, n: number): string;
}
  • 類的合并
    類的合并與接口的合并規(guī)則一致。

參考
TypeScript入門教程

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

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