看完這篇還敢說不會TypeScript嗎?

一篇長文總結 TypeScript 須要掌握的基礎,但願對正在學或者想學 TypeScript 的看完這篇文章後的你,會對 TypeScript 有一個初步的理解。html

本文偏長,點贊👍獲取一塊記憶麪包typescript

1.爲何選擇 TypeScript?

  • 類型系統(全部的函數和變量都有定義的類型)
  • 靜態檢查(類型註解提供編譯時的靜態類型檢查,減小編譯時才找到bug的痛苦)
  • 文檔清晰(只要函數的類型定義明瞭,別人用你的函數就不會摸不着頭腦)

2.安裝TypeScript

新建一個文件夾,如 TypeScript-Study ,輸入以下命令行,在全局環境下安裝 tsc 命令npm

npm install -g typescript
複製代碼

安裝完成以後,咱們就能夠在任何地方執行 tsc 命令了。編程

3.編寫一個 ts 文件

新建一個簡單的文件 hello.ts ,複製以下代碼到文件中,這裏建議使用 VS Code 編譯器,內置了 TypeScript 支持,並且自己也是用 TypeScript 編寫的(畢竟微軟的親兒子)json

let hello: string = 'hello typescript'
console.log(hello)
複製代碼

而後輸入下面命令行執行後端

tsc hello.ts
複製代碼

這時會發現文件夾多出了一個編譯好的 js 文件 hello.js數組

var hello = 'hello typescript';
console.log(hello);
複製代碼

這裏能夠發現,在 ts 文件中使用 : 指定變量類型。bash

上面例子中咱們給 hello 參數指定了 string 類型,編譯成 JavaScript 時,檢查的代碼並不會被插入到 js 文件中。編程語言

這是由於 TypeScript 只會進行靜態檢查,若是發現有錯誤,編譯的時候就會報錯。編輯器

下面修改一下代碼,把定義了 string 類型的參數改成 number 類型的參數

let hello: string = 'hello typescript'
hello = 2
console.log(hello)
複製代碼

編輯器中會提示錯誤,編譯的時候也會出錯

但仍然不妨礙咱們執行生成 js 文件

var hello = 'hello typescript';
hello = 2;
console.log(hello);
複製代碼

若是要在報錯的時候終止 js 文件的生成,能夠在 tsconfig.json 中配置 noEmitOnError 便可,這裏就不展開說明。

4.數據類型

JavaScript 的類型分爲兩種:原始數據類型和對象類型。

4.1原始數據類型

原始數據類型包括:布爾值、數值、字符串、nullundefined 以及 Symbol

布爾值

TypeScript 中,使用 boolean 定義布爾值類型

let isBoolean: boolean = true;
複製代碼

注意:要注意 boolean 和 Boolean 的大小寫區別, boolean 是布爾值類型,而 Boolean 是構造函數。這裏除了 null 和 undefined 以外,別的基本類型都同樣。

數值

使用 number 定義數值類型

// 基本用法
let decLiteral: number = 1234567;
let hexLiteral: number = 0xf0ac;
let notANumber: number = NaN;
let infinity: number = Infinity;
複製代碼

編譯結果:

// 基本用法
var decLiteral = 1234567;
var hexLiteral = 0xf0ac;
var notANumber = NaN;
var infinity = Infinity;
複製代碼

字符串

使用 string 定義字符串類型

let name: string = 'sam';
複製代碼

Undefined 和 Null

使用 nullundefined 來定義這兩個原始數據類型:

let u: undefined = undefined;
let n: null = null;
複製代碼

4.2對象類型

TypeScript 中,咱們使用 interfaces 來定義對象類型。

肯定屬性

我們直接舉例說明吧

interface IPerson {
    name: string;
    age: number;
}

let sam: IPerson = {
    name: 'sam',
    age: 20
};
複製代碼

上面的例子中,咱們定義了一個接口 IPerson ,而後定義了一個變量 sam ,變量的類型是 IPerson,這樣就約束了 sam 的形狀必須和接口 IPerson 一致。

注意:爲了良好的編寫習慣,建議接口的名稱加上 I 前綴。

定義的變量 sam 比接口多或者少屬性都是會報錯的

interface IPerson {
    name: string;
    age: number;
}

// 錯誤(比接口少了個age屬性)
let sam1: IPerson = {
    name: 'sam'
};

// 錯誤(比接口多了個gender屬性)
let sam2: IPerson = {
    name: 'sam',
    age: 20,
    gender: 'man'
};

// 正確
let sam3: IPerson = {
    name: 'sam',
    age: 20
};
複製代碼

那麼怎麼解決這個問題呢,這裏有個可選屬性

可選屬性

在接口上某個屬性加個 ? ,代表不須要強制匹配該屬性

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

// 正確
let sam1: IPerson = {
    name: 'sam'
};

// 錯誤(此時咱們仍是不能在接口上添加未定義的屬性)
let sam2: IPerson = {
    name: 'sam',
    gender: 'man'
};
複製代碼

爲了解決添加未定義的屬性,咱們可使用任意屬性

任意屬性

在須要添加任意屬性的接口使用 [propName: string]一旦定義了任意屬性,那麼肯定屬性和可選屬性的類型都必須是它的類型的子集

interface IPerson {
    name: string;
    // 此時的age類型爲number,不是string類型的子集
    age?: number;
    // 肯定屬性和可選屬性的類型都必須是string類型的子集
    [propName: string]: string;
}

// 錯誤(此時的age類型爲number,不是string類型的子集)
let sam1: IPerson = {
    name: 'sam',
    age: 20,
    gender: 'man'
};
複製代碼

那麼怎麼解決這個問題呢,一個接口中只能定義一個任意屬性。若是接口中有多個類型的屬性,則能夠在任意屬性中使用聯合類型

interface IPerson {
    name: string;
    age?: number;
    // 此時肯定屬性和可選屬性的類型都必須是string或者number類型的子集
    [propName: string]: string | number;
}

// 正確
let sam1: IPerson = {
    name: 'sam',
    age: 20,
    gender: 'man'
};

// 正確
let sam2: IPerson = {
    name: 'sam',
    gender: 'man'
};

// 正確
let sam3: IPerson = {
    name: 'sam'
};

// 錯誤,任意屬性未添加boolean類型
let sam4: IPerson = {
    name: 'sam',
    rich: false
};
複製代碼

只讀屬性

對象中的一些字段只能在建立的時候被賦值,後續沒法更改

interface IPerson {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: string | number;
}

let sam: IPerson = {
    id: 123,
    name: 'Tom',
    gender: 'male'
};

// 錯誤,此時不能再次修改id的值
sam.id = 9527;

// Cannot assign to 'id' because it is a read-only property.
複製代碼

到這,我想你大概就瞭解了對象的類型(接口)了

5.任意值類型(any)

還記得上面的例子中,咱們在給 hello 賦值的時候編譯器會報錯

let hello: string = 'hello typescript'
hello = 2
console.log(hello)

// Type '2' is not assignable to type 'string'.ts(2322)
複製代碼

若是把 hello 改成 any 類型,則代表 hello 能夠賦值爲任意類型。

let hello: any = 'hello typescript'
hello = 2
console.log(hello) // 2
複製代碼

固然這裏不是但願你全部的類型都用 any ,這還寫個🔨的 TypeScript ,這裏通常建議引入第三方庫或者實在沒法確認在處理什麼類型時的時候才使用。

6.類型推論

若是沒有明確的指定類型,那麼 TypeScript 會依照類型推論(Type Inference)的規則推斷出一個類型。

下面代碼 hello 沒有指定類型,但在編譯器和編譯時也會報錯誤提醒

let hello = 'hello typescript'
hello = 2

// Type '2' is not assignable to type 'string'.ts(2322)
複製代碼

由於它等價於

let hello: string = 'hello typescript'
hello = 2
複製代碼

可是若是定義的時候沒有賦值,無論以後有沒有賦值,都會被推斷成 any 類型

let hello
hello = 'string2'
hello = 2
複製代碼

等價於

let hello: any
hello = 'string2'
hello = 2
複製代碼

7.聯合類型

有時候咱們但願聲明一個變量時候包含多個類型,那麼咱們可使用 | 分隔每一個類型。還記得上面的例子嗎

let hello: string = 'hello typescript'
hello = 2
console.log(hello)
複製代碼

此時咱們修改一下 hello 參數

let hello: string | number = 'hello typescript'
hello = 2
console.log(hello)
複製代碼

這樣就代表 hello 參數接受 stringnumber 類型

8.數組的類型

TypeScript 中,數組類型有多個定義的方式。因爲本文是基礎篇,筆者只講比較經常使用的幾種方法方便你們理解,後續進階的用法筆者會另開文章說明。

直接定義

let arr1: number[] = [1, 2, 3, 4, 5];
let arr2: string[] = ["one", "two", "third", "four", "five"];
let arr3: any[] = [1, "two", false, "four", 5];
複製代碼

但這裏要注意,定義後的數組 arr 的項中不容許出現非其餘的類型( any 除外)

// 錯誤(two爲string類型)
let arr1: number[] = [1, 'two', 3, 4, 5];

let arr2: number[] = [1, 2, 3, 4, 5];
// 錯誤(定義後的arr,push一個string類型是錯誤的)
arr2.push("six")
複製代碼

數組泛型

用數組泛型(Array Generic) Array<elemType> 來表示數組

泛型(Generics)是指在定義函數、接口或類的時候,不預先指定具體的類型,而在使用的時候再指定類型的一種特性,這裏不作過多描述。

let arr1: Array<number> = [1, 2, 3, 4, 5];
let arr2: Array<string> = ["one", "two", "third", "four", "five"];
let arr3: Array<string | number | boolean> = [1, "two", false, "four", 5];
let arr4: Array<any> = [1, "two", false, "four", 5];
複製代碼

9.函數的類型

咱們先來複習一下 JavaScript 裏面聲明常見的定義函數的方式(函數聲明和函數表達式)

// 函數聲明
function sum1(x + y) {
    return x + y;
}

// 函數表達式
let sum2 = function (x, y) {
    return 
}
複製代碼

9.1 函數聲明

TypeScript 聲明函數時,咱們須要把函數的輸入和輸出都要考慮在內

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

// 用法
// 錯誤
sum(1);
sum(1, 2, 3);

// 正確
sum(1, 2);
複製代碼

可見,定義好的函數,若是輸入了多的或者少的參數都是不被 TypeScript 容許的。

9.2函數表達式

你可能會這樣寫一個函數表達式

let sum2 = function (x: number, y: number): number {
    return x + y;
};
複製代碼

這樣只對右邊的匿名函數進行了類型定義,而左邊的參數 sum2 是經過類型推斷出來的,並不算一個完整的 TypeScript 表達式。正確寫法以下

let sum2: (x: number, y: number) => number = function(x: number, y: number): number {
    return x + y
}
複製代碼

注意:在 TypeScript 的類型定義中的 => 用來表示函數的定義,箭頭左邊是輸入類型,箭頭右邊是輸出類型,不要和 ES6 的箭頭函數混淆。

9.3可選參數

和接口的可選屬性類似,咱們也能夠用 ? 來給函數表示可選的參數

function sum3 (x:number, y?: number) {
    return x + y
}

// 用法
sum3(1,2)
sum3(1)
複製代碼

注意:可選參數的後面不容許放必須參數,由於這樣調用的時候沒法識別。

除非添加參數默認值,則可無視這個限制

// 錯誤寫法(可選參數y後面跟着必須參數z)
function sum3 (x:number, y?:number, z: number) {
    return x + y + z
}

// 給可選參數y添加參數默認值
function sum4 (x:number, y:number = 1, z: number) {
    return x + y + z
}
複製代碼

10.類型斷言

用來手動指定一個值的類型叫作類型斷言。

語法

有兩種寫法,尖括號 <類型>as

let str: string = "this is a string";

// 尖括號<>
let strLength1: number = (<string>str).length;
// as
let strLength2: number = (str as string).length;
複製代碼

這裏建議你們統一使用 as 語法,由於在 React 的 tsx 語法中必須使用 as。

TypeScript 不肯定一個聯合類型的變量究竟是哪一個類型的時候,咱們只能訪問此聯合類型的全部類型裏共有的屬性或方法,如

function getLength(str: string | number): number {
    // 錯誤,由於 number 類型沒有 length 方法
    return str.length;
}

// Property 'length' does not exist on type 'number'.ts(2339)
複製代碼

爲此,咱們須要用到類型斷言

function getLength(str: string | number): number {
    if (typeof str === 'number') {
        // 此時將 str 的類型判斷爲 number 類型,可使用 number 類型的屬性和方法
        return (str as number).toString().length
    } else if (typeof str === 'string') {
        // 此時將 str 的類型判斷爲 string 類型,可使用 string 類型的屬性和方法
        return (str as string).length
    } else {
        throw 'error'
    }
}
複製代碼

恭喜你👏看到這裏,TypeScript 最爲須要掌握的基礎你已經學完了,不太重頭戲纔剛剛開始😅

11.枚舉(Enum)

枚舉是 TypeScript 中,對 JavaScript 標準數據類型的補充,例如一個 Http 包含哪些狀態,一星期是從星期一到星期日等,咱們均可以用枚舉表達,若是你用事後端的編程語言,應該對枚舉不陌生。

不一樣於對象定義的 key-value 中,只能經過 key 去訪問 value 的值,在 enum 中,既能夠經過 key 訪問 value 的值,也能夠經過 value 訪問 key 的值。

枚舉用 enum 關鍵字來定義

簡單定義

enum Person {
    Male,
    Female
}
複製代碼

上面的代碼編譯以下

var Person;
(function (Person) {
    Person[Person["Male"] = 0] = "Male";
    Person[Person["Female"] = 1] = "Female";
})(Person || (Person = {}));
複製代碼

可見,若沒給枚舉的成員賦值,那麼會默認從 0 開始遞增。

固然咱們也能夠給枚舉手動賦值

手動賦值

enum Person {
    Male = 7,
    Female      // 此時 Female 會爲 8
}
// 由於未賦值的枚舉會接着上一個枚舉項遞增,所以此時 Female 會爲 8
複製代碼

上面提到的都是常數項,其實枚舉還有一個類型叫 計算所得項

enum Person {
    Male,                    // 常數項
    Female = "female".length // 計算所得項
}

// 若是計算所得項後面是沒有賦值的項,則會報錯
// 錯誤
enum Person {
    Male = "man".length,
    Female
}

// 正確
enum Person {
    Male = "man".length,
    Female = 8
}
複製代碼

常數枚舉

常數枚舉是用 const enum 定義的,常數枚舉在編譯的階段會被刪除,既在編譯後的文件不存在編譯後的常數枚舉,且不能包含計算成員

// 正確
const enum Person {
    Male,
    Female
}

// 錯誤
const enum Person2 {
    Male,
    Female = "female".length
}

let person = [Person.Male, Person.Female]
複製代碼

編譯結果

var person = [0 /* Male */, 1 /* Female */];
複製代碼

外部枚舉

外部枚舉是用 declare enum 定義的,外部枚舉用來描述已經存在的枚舉類型的形狀。

declare enum Person {
    Male,
    Female
}

let person = [Person.Male, Person.Female]
複製代碼

編譯結果

var person = [Person.Male, Person.Female];
複製代碼

12.元組(Tuple)

元組合並了不一樣類型的對象,須要以元組所定義的順序預約數據類型。

下面舉個簡單的例子

let tuple1: [string, number];

// 正確
tuple1 = ["one", 2];

// 錯誤
tuple1 = ["one", "two"];
tuple1 = [1, 2];
tuple1 = ["one", 2, 3]; // 未在元組中定義
tuple1 = [true, 2]      // 元組在index爲0中只接受string類型
複製代碼

13.泛型(Generics)

還記得上文數組泛型的定義嗎

let arr1: Array<number> = [1, 2, 3, 4, 5];
複製代碼

這裏的 Array<number> 只容許數組的每一項都要爲 number 類型,但有的時候,咱們但願返回值的類型與傳入參數的類型是相同的,所以有了泛型。

這裏是官方文檔的例子:

function identity<T>(arg: T): T {
    return arg;
}
複製代碼

這裏的 identity 函數就被稱爲泛型,由於它能夠用於多個類型,咱們給 identity 添加了類型變量 TT 會幫助咱們捕獲用戶傳入的類型(好比:number),以後咱們就可使用這個類型。 以後咱們再次使用了 T 當作返回值類型。

定義了泛型以後,咱們能夠這樣使用

let output1 = identity<string>("myString");

// 類型推論。編譯器會根據傳入的參數自動地幫助咱們肯定 T 的類型

// 此時的 T 爲 string 類型
let output2 = identity("myString");
// 此時的 T 爲 number 類型
let out2 = identity(123)
// 此時的 T 爲 boolean 類型
let out3 = identity(true)
複製代碼

泛型約束

若是咱們想打印 arg 的長度,會發現編譯器報錯。

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

這是由於泛型 T 是默認把 arg 參數看成是任意或者全部類型,而 number 類型沒有 length 屬性,因此會報錯。

若是咱們傳入數字數組,將返回一個數字數組,由於此時 T 的的類型爲 number。 這可讓咱們把泛型變量 T 當作類型的一部分使用,而不是整個類型,增長了靈活性。

function loggingIdentity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // arg
    return arg;
}
複製代碼

泛型接口

咱們把上面例子的對象字面量換位接口

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

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

let myIdentity: Iidentity = identity;
複製代碼

泛型類

與泛型接口相似,泛型也能夠用於類的類型定義中

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; };
複製代碼

14.參考文章

15.結尾

看到這裏,你還敢說不了解 TypeScript 嗎?若是還不瞭解,那必定是你還沒給這篇文章點贊👍👍👍!!!

看得不過癮?這裏還有別的文章

相關文章
相關標籤/搜索