想學習 TypeScript 的小伙伴看過來,本文將帶你一步步學習 TypeScript 入門相關(guān)的十四個知識點,,詳細的內(nèi)容大綱請看下圖:
一,、TypeScript 是什么
TypeScript 是一種由微軟開發(fā)的自由和開源的編程語言。它是 JavaScript 的一個超集,,而且本質(zhì)上向這個語言添加了可選的靜態(tài)類型和基于類的面向?qū)ο缶幊獭?/span>
TypeScript 提供最新的和不斷發(fā)展的 JavaScript 特性,,包括那些來自 2015 年的 ECMAScript 和未來的提案中的特性,比如異步功能和 Decorators,,以幫助建立健壯的組件,。下圖顯示了 TypeScript 與 ES5、ES2015 和 ES2016 之間的關(guān)系:
1.1 TypeScript 與 JavaScript 的區(qū)別
1.2 獲取 TypeScript
命令行的 TypeScript 編譯器可以使用 Node.js 包來安裝,。
1.安裝 TypeScript
$ npm install -g typescript
2.編譯 TypeScript 文件
$ tsc helloworld.ts# helloworld.ts => helloworld.js
當然,,對于剛?cè)腴T TypeScript 的小伙伴,也可以不用安裝 typescript ,,而是直接使用線上的 TypeScript Playground 來學習新的語法或新特性,。
二,、TypeScript 基礎(chǔ)類型
2.1 Boolean 類型
let isDone: boolean = false;
2.2 Number 類型
let count: number = 10;
String 類型
let name: string = "Semliker";
2.4 Array 類型
let list: number[] = [1, 2, 3];
2.5 Enum 類型
使用枚舉我們可以定義一些帶名字的常量。 使用枚舉可以清晰地表達意圖或創(chuàng)建一組有區(qū)別的用例,。 TypeScript 支持數(shù)字的和基于字符串的枚舉,。
1.數(shù)字枚舉
enum Direction { NORTH, SOUTH, EAST, WEST,}let dir: Direction = Direction.NORTH;
默認情況下,NORTH 的初始值為 0,,其余的成員會從 1 開始自動增長,。換句話說,,Direction.SOUTH 的值為 1,,Direction.EAST 的值為 2,Direction.WEST 的值為 3,。上面的枚舉示例代碼經(jīng)過編譯后會生成以下代碼:
? enum Direction { NORTH, SOUTH, EAST, WEST, } ? let dir: Direction = Direction.NORTH;
當然我們也可以設(shè)置 NORTH 的初始值,,比如:
enum Direction { NORTH = 3, SOUTH, EAST, WEST,}
2.字符串枚舉
在 TypeScript 2.4 版本,允許我們使用字符串枚舉,。在一個字符串枚舉里,,每個成員都必須用字符串字面量,或另外一個字符串枚舉成員進行初始化,。
enum Direction { NORTH = "NORTH", SOUTH = "SOUTH", EAST = "EAST", WEST = "WEST",}
以上代碼對于的 ES5 代碼如下:
? "use strict"; var Direction; (function (Direction) { Direction["NORTH"] = "NORTH"; Direction["SOUTH"] = "SOUTH"; Direction["EAST"] = "EAST"; Direction["WEST"] = "WEST"; })(Direction || (Direction = {}));
3.異構(gòu)枚舉
異構(gòu)枚舉的成員值是數(shù)字和字符串的混合:
enum Enum { A, B, C = "C", D = "D", E = 8, F,}
以上代碼對于的 ES5 代碼如下:
? enum Enum { A, B, C = "C", D = "D", E = 8, F, }
通過觀察上述生成的 ES5 代碼,,我們可以發(fā)現(xiàn)數(shù)字枚舉相對字符串枚舉多了 “反向映射”:
console.log(Enum.A)
2.6 Any 類型
在 TypeScript 中,任何類型都可以被歸為 any 類型,。這讓 any 類型成為了類型系統(tǒng)的頂級類型(也被稱作全局超級類型)。
let notSure: any = 666;notSure = "Semlinker";notSure = false;
any 類型本質(zhì)上是類型系統(tǒng)的一個逃逸艙。作為開發(fā)者,,這給了我們很大的自由:TypeScript 允許我們對 any 類型的值執(zhí)行任何操作,,而無需事先執(zhí)行任何形式的檢查,。比如:
let value: any;value.foo.bar;
在許多場景下,這太寬松了,。使用 any 類型,,可以很容易地編寫類型正確但在運行時有問題的代碼,。如果我們使用 any 類型,,就無法使用 TypeScript 提供的大量的保護機制,。為了解決 any 帶來的問題,,TypeScript 3.0 引入了 unknown 類型,。
2.7 Unknown 類型
就像所有類型都可以賦值給 any ,,所有類型也都可以賦值給 unknown 。這使得 unknown 成為 TypeScript 類型系統(tǒng)的另一種頂級類型(另一種是 any )。下面我們來看一下 unknown 類型的使用示例:
? let value: unknown; ? value = true;
對 value 變量的所有賦值都被認為是類型正確的。但是,當我們嘗試將類型為 unknown 的值賦值給其他類型的變量時會發(fā)生什么,?
let value: unknown;let value1: unknown = value; // OKlet value2: any = value; // OKlet value3: boolean = value; // Errorlet value4: number = value; // Errorlet value5: string = value; // Errorlet value6: object = value; // Errorlet value7: any[] = value; // Errorlet value8: Function = value; // Error
unknown 類型只能被賦值給 any 類型和 unknown 類型本身,。直觀地說,這是有道理的:只有能夠保存任意類型值的容器才能保存 unknown 類型的值,。畢竟我們不知道變量 value 中存儲了什么類型的值。
現(xiàn)在讓我們看看當我們嘗試對類型為 unknown 的值執(zhí)行操作時會發(fā)生什么。以下是我們在之前 any 章節(jié)看過的相同操作:
? let value: unknown; ? let value1: unknown = value;
將 value 變量類型設(shè)置為 unknown 后,這些操作都不再被認為是類型正確的。通過將 any 類型改變?yōu)?unknown 類型,,我們已將允許所有更改的默認設(shè)置,,更改為禁止任何更改。
2.8 Tuple 類型
眾所周知,數(shù)組一般由同種類型的值組成,但有時我們需要在單個變量中存儲不同類型的值,,這時候我們就可以使用元組,。在 JavaScript 中是沒有元組的,,元組是 TypeScript 中特有的類型,,其工作方式類似于數(shù)組。
元組可用于定義具有有限數(shù)量的未命名屬性的類型,。每個屬性都有一個關(guān)聯(lián)的類型,。使用元組時,必須提供每個屬性的值,。為了更直觀地理解元組的概念,,我們來看一個具體的例子:
let tupleType: [string, boolean];tupleType = ["Semlinker", true];
在上面代碼中,我們定義了一個名為 tupleType 的變量,,它的類型是一個類型數(shù)組 [string, boolean] ,,然后我們按照正確的類型依次初始化 tupleType 變量。與數(shù)組一樣,,我們可以通過下標來訪問元組中的元素:
console.log(tupleType[0]);
在元組初始化的時候,,如果出現(xiàn)類型不匹配的話,比如:
tupleType = [true, "Semlinker"];
此時,,TypeScript 編譯器會提示以下錯誤信息:
[0]: Type 'true' is not assignable to type 'string'.[1]: Type 'string' is not assignable to type 'boolean'.
很明顯是因為類型不匹配導致的,。在元組初始化的時候,我們還必須提供每個屬性的值,,不然也會出現(xiàn)錯誤,,比如:
tupleType = ["Semlinker"];
此時,TypeScript 編譯器會提示以下錯誤信息:
Property '1' is missing in type '[string]' but required in type '[string, boolean]'.
2.9 Void 類型
某種程度上來說,,void 類型像是與 any 類型相反,,它表示沒有任何類型。當一個函數(shù)沒有返回值時,,你通常會見到其返回值類型是 void:
以上代碼編譯生成的 ES5 代碼如下:
"use strict";function warnUser() { console.log("This is my warning message");}
需要注意的是,聲明一個 void 類型的變量沒有什么作用,,因為它的值只能為 undefined 或 null :
let unusable: void = undefined;
2.10 Null 和 Undefined 類型
TypeScript 里,,undefined 和 null 兩者有各自的類型分別為 undefined 和 null 。
let u: undefined = undefined;let n: null = null;
默認情況下 null 和 undefined 是所有類型的子類型,。 就是說你可以把 null 和 undefined 賦值給 number 類型的變量,。然而,如果你指定了--strictNullChecks 標記,,null 和 undefined 只能賦值給 void 和它們各自的類型,。
2.11 Never 類型
never 類型表示的是那些永不存在的值的類型,。 例如,never 類型是那些總是會拋出異?;蚋揪筒粫蟹祷刂档暮瘮?shù)表達式或箭頭函數(shù)表達式的返回值類型,。
?
在 TypeScript 中,可以利用 never 類型的特性來實現(xiàn)全面性檢查,,具體示例如下:
? type Foo = string | number; ? function controlFlowAnalysisWithNever(foo: Foo) { if (typeof foo === "string") {
注意在 else 分支里面,,我們把收窄為 never 的 foo 賦值給一個顯示聲明的 never 變量。如果一切邏輯正確,,那么這里應(yīng)該能夠編譯通過,。但是假如后來有一天你的同事修改了 Foo 的類型:
type Foo = string | number | boolean;
然而他忘記同時修改 controlFlowAnalysisWithNever 方法中的控制流程,這時候 else 分支的 foo 類型會被收窄為 boolean 類型,,導致無法賦值給 never 類型,,這時就會產(chǎn)生一個編譯錯誤。通過這個方式,,我們可以確保
controlFlowAnalysisWithNever 方法總是窮盡了 Foo 的所有可能類型,。 通過這個示例,我們可以得出一個結(jié)論:使用 never 避免出現(xiàn)新增了聯(lián)合類型沒有對應(yīng)的實現(xiàn),,目的就是寫出類型絕對安全的代碼,。
三、TypeScript 斷言
有時候你會遇到這樣的情況,,你會比 TypeScript 更了解某個值的詳細信息,。通常這會發(fā)生在你清楚地知道一個實體具有比它現(xiàn)有類型更確切的類型。
通過類型斷言這種方式可以告訴編譯器,,“相信我,,我知道自己在干什么”。類型斷言好比其他語言里的類型轉(zhuǎn)換,,但是不進行特殊的數(shù)據(jù)檢查和解構(gòu),。它沒有運行時的影響,只是在編譯階段起作用,。
類型斷言有兩種形式:
3.1 “尖括號” 語法
? let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;
3.2 as 語法
? let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
四,、類型守衛(wèi)
A type guard is some expression that performs a runtime check that guarantees the type in some scope. —— TypeScript 官方文檔
類型保護是可執(zhí)行運行時檢查的一種表達式,用于確保該類型在一定的范圍內(nèi),。換句話說,,類型保護可以保證一個字符串是一個字符串,盡管它的值也可以是一個數(shù)值,。類型保護與特性檢測并不是完全不同,,其主要思想是嘗試檢測屬性、方法或原型,,以確定如何處理值,。目前主要有四種的方式來實現(xiàn)類型保護:
4.1 in 關(guān)鍵字
? interface Admin { name: string; privileges: string[]; } ? interface Employee { name: string; startDate: Date; } ? type UnknownEmployee = Employee | Admin; ? function printEmployeeInformation(emp: UnknownEmployee) { console.log("Name: " + emp.name); if ("privileges" in emp) { console.log("Privileges: " + emp.privileges); } if ("startDate" in emp) { console.log("Start Date: " + emp.startDate); } }
4.2 typeof 關(guān)鍵字
? 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 類型保護只支持兩種形式:typeof v === "typename" 和 typeof v !== typename ,,"typename" 必須是 "number" , "string" ,, "boolean" 或 "symbol" ,。 但是 TypeScript 并不會阻止你與其它字符串比較,語言不會把那些表達式識別為類型保護,。
4.3 instanceof 關(guān)鍵字
? 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; } } ? let padder: Padder = new SpaceRepeatingPadder(6); ? if (padder instanceof SpaceRepeatingPadder) {
4.4 自定義類型保護的類型謂詞
? function isNumber(x: any): x is number { return typeof x === "number"; } ? function isString(x: any): x is string { return typeof x === "string"; }
五,、聯(lián)合類型和類型別名
5.1 聯(lián)合類型
聯(lián)合類型通常與 null 或 undefined 一起使用:
const sayHello = (name: string | undefined) => {
例如,這里 name 的類型是 string | undefined 意味著可以將 string 或 undefined 的值傳遞給sayHello 函數(shù),。
sayHello("Semlinker");sayHello(undefined);
通過這個示例,,你可以憑直覺知道類型 A 和類型 B 聯(lián)合后的類型是同時接受 A 和 B 值的類型。
5.2 可辨識聯(lián)合
TypeScript 可辨識聯(lián)合(Discriminated Unions)類型,,也稱為代數(shù)數(shù)據(jù)類型或標簽聯(lián)合類型,。它包含 3 個要點:可辨識、聯(lián)合類型和類型守衛(wèi),。
這種類型的本質(zhì)是結(jié)合聯(lián)合類型和字面量類型的一種類型保護方法,。如果一個類型是多個類型的聯(lián)合類型,且多個類型含有一個公共屬性,,那么就可以利用這個公共屬性,,來創(chuàng)建不同的類型保護區(qū)塊。
1.可辨識
可辨識要求聯(lián)合類型中的每個元素都含有一個單例類型屬性,,比如:
? enum CarTransmission { Automatic = 200, Manual = 300 } ? interface Motorcycle { vType: "motorcycle";
在上述代碼中,,我們分別定義了 Motorcycle 、 Car 和 Truck 三個接口,,在這些接口中都包含一個 vType 屬性,,該屬性被稱為可辨識的屬性,而其它的屬性只跟特性的接口相關(guān),。
2.聯(lián)合類型
基于前面定義了三個接口,,我們可以創(chuàng)建一個 Vehicle 聯(lián)合類型:
type Vehicle = Motorcycle | Car | Truck;
現(xiàn)在我們就可以開始使用 Vehicle 聯(lián)合類型,對于 Vehicle 類型的變量,,它可以表示不同類型的車輛,。
3.類型守衛(wèi)
下面我們來定義一個 evaluatePrice 方法,該方法用于根據(jù)車輛的類型,、容量和評估因子來計算價格,,具體實現(xiàn)如下:
? const EVALUATION_FACTOR = Math.PI; function evaluatePrice(vehicle: Vehicle) { return vehicle.capacity * EVALUATION_FACTOR; } ? const myTruck: Truck = { vType: "truck", capacity: 9.5 }; evaluatePrice(myTruck);
對于以上代碼,TypeScript 編譯器將會提示以下錯誤信息:
? Property 'capacity' does not exist on type 'Vehicle'. Property 'capacity' does not exist on type 'Motorcycle'.
原因是在 Motorcycle 接口中,,并不存在 capacity 屬性,而對于 Car 接口來說,,它也不存在 capacity 屬性,。那么,,現(xiàn)在我們應(yīng)該如何解決以上問題呢?這時,,我們可以使用類型守衛(wèi),。下面我們來重構(gòu)一下前面定義的 evaluatePrice 方法,重構(gòu)后的代碼如下:
? function evaluatePrice(vehicle: Vehicle) { switch(vehicle.vType) { case "car": return vehicle.transmission * EVALUATION_FACTOR; case "truck": return vehicle.capacity * EVALUATION_FACTOR; case "motorcycle": return vehicle.make * EVALUATION_FACTOR; } }
在以上代碼中,,我們使用 switch 和 case 運算符來實現(xiàn)類型守衛(wèi),,從而確保在 evaluatePrice 方法中,我們可以安全地訪問 vehicle 對象中的所包含的屬性,,來正確的計算該車輛類型所對應(yīng)的價格,。
5.3 類型別名
類型別名用來給一個類型起個新名字。
type Message = string | string[];let greet = (message: Message) => {
六,、交叉類型
TypeScript 交叉類型是將多個類型合并為一個類型,。 這讓我們可以把現(xiàn)有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性,。
? interface IPerson { id: string; age: number; } ? interface IWorker { companyId: string; } ? type IStaff = IPerson & IWorker; ? const staff: IStaff = { id: 'E1006', age: 33, companyId: 'EFT' }; ? console.dir(staff)
在上面示例中,,我們首先為 IPerson 和 IWorker 類型定義了不同的成員,然后通過 & 運算符定義了 IStaff 交叉類型,,所以該類型同時擁有 IPerson 和 IWorker 這兩種類型的成員,。
七、TypeScript 函數(shù)
7.1 TypeScript 函數(shù)與 JavaScript 函數(shù)的區(qū)別
7.2 箭頭函數(shù)
1.常見語法
? myBooks.forEach(() => console.log('reading')); ? myBooks.forEach(title => console.log(title)); ? myBooks.forEach((title, idx, arr) => console.log(idx + '-' + title); ); ? myBooks.forEach((title, idx, arr) => { console.log(idx + '-' + title); });
2.使用示例
?
7.3 參數(shù)類型和返回類型
? function createUserId(name: string, id: number): string { return name + id; }
7.4 函數(shù)類型
? let IdGenerator: (chars: string, nums: number) => string; ? function createUserId(name: string, id: number): string { return name + id; } ? IdGenerator = createUserId;
7.5 可選參數(shù)及默認參數(shù)
?
在聲明函數(shù)時,,可以通過 ? 號來定義可選參數(shù),,比如 age?: number 這種形式。在實際使用時,,需要注意的是可選參數(shù)要放在普通參數(shù)的后面,,不然會導致編譯錯誤。
7.6 剩余參數(shù)
? function push(array, ...items) { items.forEach(function (item) { array.push(item); }); } ? let a = []; push(a, 1, 2, 3);
7.7 函數(shù)重載
函數(shù)重載或方法重載是使用相同名稱和不同參數(shù)數(shù)量或類型創(chuàng)建多個方法的一種能力,。要解決前面遇到的問題,,方法就是為同一個函數(shù)提供多個函數(shù)類型定義來進行函數(shù)重載,編譯器會根據(jù)這個列表去處理函數(shù)的調(diào)用,。
? function add(a: number, b: number): number; function add(a: string, b: string): string; function add(a: string, b: number): string; function add(a: number, b: string): string; function add(a: Combinable, b: Combinable) { if (typeof a === "string" || typeof b === "string") { return a.toString() + b.toString(); } return a + b; }
在以上代碼中,,我們?yōu)?add 函數(shù)提供了多個函數(shù)類型定義,從而實現(xiàn)函數(shù)的重載,。之后,,可惡的錯誤消息又消失了,因為這時 result 變量的類型是 string 類型,。在 TypeScript 中除了可以重載普通函數(shù)之外,,我們還可以重載類中的成員方法。
方法重載是指在同一個類中方法同名,,參數(shù)不同(參數(shù)類型不同,、參數(shù)個數(shù)不同或參數(shù)個數(shù)相同時參數(shù)的先后順序不同),,調(diào)用時根據(jù)實參的形式,選擇與它匹配的方法執(zhí)行操作的一種技術(shù),。所以類中成員方法滿足重載的條件是:在同一個類中,,方法名相同且參數(shù)列表不同。下面我們來舉一個成員方法重載的例子:
? class Calculator { add(a: number, b: number): number; add(a: string, b: string): string; add(a: string, b: number): string; add(a: number, b: string): string; add(a: Combinable, b: Combinable) { if (typeof a === "string" || typeof b === "string") { return a.toString() + b.toString(); } return a + b; } } ? const calculator = new Calculator(); const result = calculator.add("Semlinker", " Kakuqo");
這里需要注意的是,,當 TypeScript 編譯器處理函數(shù)重載時,,它會查找重載列表,嘗試使用第一個重載定義,。 如果匹配的話就使用這個,。 因此,在定義重載的時候,,一定要把最精確的定義放在最前面,。另外在 Calculator 類中,add(a: Combinable, b: Combinable){ } 并不是重載列表的一部分,,因此對于 add 成員方法來說,,我們只定義了四個重載方法。
八,、TypeScript 數(shù)組
8.1 數(shù)組解構(gòu)
? let x: number; let y: number; let z: number; let five_array = [0,1,2,3,4]; [x,y,z] = five_array;
8.2 數(shù)組展開運算符
let two_array = [0, 1]; let five_array = [...two_array, 2, 3, 4];
8.3 數(shù)組遍歷
let colors: string[] = ["red", "green", "blue"]; for (let i of colors) { console.log(i); }
九,、TypeScript 對象
9.1 對象解構(gòu)
? let person = { name: "Semlinker", gender: "Male", }; ? let { name, gender } = person;
9.2 對象展開運算符
? let person = { name: "Semlinker", gender: "Male", address: "Xiamen", }; ?
十、TypeScript 接口
在面向?qū)ο笳Z言中,,接口是一個很重要的概念,,它是對行為的抽象,而具體如何行動需要由類去實現(xiàn),。
TypeScript 中的接口是一個非常靈活的概念,,除了可用于對類的一部分行為進行抽象以外,也常用于對「對象的形狀(Shape)」進行描述,。
10.1 對象的形狀
? interface Person { name: string; age: number; } ? let Semlinker: Person = { name: "Semlinker", age: 33, };
10.2 可選 | 只讀屬性
? interface Person { readonly name: string; age?: number; }
只讀屬性用于限制只能在對象剛剛創(chuàng)建的時候修改其值,。此外 TypeScript 還提供了 ReadonlyArray 類型,它與 Array 相似,,只是把所有可變方法去掉了,,因此可以確保數(shù)組創(chuàng)建后再也不能被修改。
? let a: number[] = [1, 2, 3, 4]; let ro: ReadonlyArray<number> = a; ro[0] = 12;
十一,、TypeScript 類
11.1 類的屬性與方法
在面向?qū)ο笳Z言中,,類是一種面向?qū)ο笥嬎銠C編程語言的構(gòu)造,是創(chuàng)建對象的藍圖,,描述了所創(chuàng)建的對象共同的屬性和方法,。
在 TypeScript 中,我們可以通過 Class 關(guān)鍵字來定義一個類:
? class Greeter {
那么成員屬性與靜態(tài)屬性,成員方法與靜態(tài)方法有什么區(qū)別呢,?這里無需過多解釋,,我們直接看一下以下編譯生成的 ES5 代碼:
? "use strict"; var Greeter =
11.2 訪問器
在 TypeScript 中,,我們可以通過 getter 和 setter 方法來實現(xiàn)數(shù)據(jù)的封裝和有效性校驗,,防止出現(xiàn)異常數(shù)據(jù)。
? let passcode = "Hello TypeScript"; ? class Employee { private _fullName: string; ? get fullName(): string { return this._fullName; } ? set fullName(newName: string) { if (passcode && passcode == "Hello TypeScript") { this._fullName = newName; } else { console.log("Error: Unauthorized update of employee!"); } } } ? let employee = new Employee(); employee.fullName = "Semlinker"; if (employee.fullName) { console.log(employee.fullName); }
11.3 類的繼承
繼承 (Inheritance) 是一種聯(lián)結(jié)類與類的層次模型,。指的是一個類(稱為子類,、子接口)繼承另外的一個類(稱為父類、父接口)的功能,,并可以增加它自己的新功能的能力,,繼承是類與類或者接口與接口之間最常見的關(guān)系。
繼承是一種 is-a 關(guān)系:
在 TypeScript 中,,我們可以通過 extends 關(guān)鍵字來實現(xiàn)繼承:
? 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); } } ? let sam = new Snake("Sammy the Python"); sam.move();
11.4 ECMAScript 私有字段
在 TypeScript 3.8 版本就開始支持ECMAScript 私有字段,,使用方式如下:
? class Person { #name: string; ? constructor(name: string) { this.#name = name; } ? greet() { console.log(`Hello, my name is ${this.#name}!`); } } ? let semlinker = new Person("Semlinker"); ? semlinker.#name;
與常規(guī)屬性(甚至使用 private 修飾符聲明的屬性)不同,,私有字段要牢記以下規(guī)則:
-
私有字段以 # 字符開頭,,有時我們稱之為私有名稱;
-
每個私有字段名稱都唯一地限定于其包含的類,;
-
不能在私有字段上使用 TypeScript 可訪問性修飾符(如 public 或 private),;
-
私有字段不能在包含的類之外訪問,,甚至不能被檢測到。
十二,、TypeScript 泛型
軟件工程中,,我們不僅要創(chuàng)建一致的定義良好的 API,同時也要考慮可重用性,。 組件不僅能夠支持當前的數(shù)據(jù)類型,,同時也能支持未來的數(shù)據(jù)類型,這在創(chuàng)建大型系統(tǒng)時為你提供了十分靈活的功能,。
在像 C# 和 Java 這樣的語言中,,可以使用泛型來創(chuàng)建可重用的組件,一個組件可以支持多種類型的數(shù)據(jù),。 這樣用戶就可以以自己的數(shù)據(jù)類型來使用組件,。
設(shè)計泛型的關(guān)鍵目的是在成員之間提供有意義的約束,這些成員可以是:類的實例成員,、類的方法,、函數(shù)參數(shù)和函數(shù)返回值。
泛型(Generics)是允許同一個函數(shù)接受不同類型參數(shù)的一種模板,。相比于使用 any 類型,,使用泛型來創(chuàng)建可復用的組件要更好,因為泛型會保留參數(shù)類型。
12.1 泛型接口
interface GenericIdentityFn<T> { (arg: T): T;}
12.2 泛型類
? 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; };
12.3 泛型變量
對剛接觸 TypeScript 泛型的小伙伴來說,,看到 T 和 E,,還有 K 和 V 這些泛型變量時,估計會一臉懵逼,。其實這些大寫字母并沒有什么本質(zhì)的區(qū)別,,只不過是一個約定好的規(guī)范而已。也就是說使用大寫字母 A-Z 定義的類型變量都屬于泛型,,把 T 換成 A,,也是一樣的。下面我們介紹一下一些常見泛型變量代表的意思:
12.4 泛型工具類型
為了方便開發(fā)者 TypeScript 內(nèi)置了一些常用的工具類型,,比如 Partial,、Required、Readonly,、Record 和 ReturnType 等,。出于篇幅考慮,這里我們只簡單介紹 Partial 工具類型,。不過在具體介紹之前,,我們得先介紹一些相關(guān)的基礎(chǔ)知識,方便讀者自行學習其它的工具類型,。
1.typeof
在 TypeScript 中,,typeof 操作符可以用來獲取一個變量聲明或?qū)ο蟮念愋汀?/span>
? interface Person { name: string; age: number; } ? const sem: Person = { name: 'semlinker', age: 30 }; type Sem= typeof sem;
2.keyof
keyof 操作符可以用來一個對象中的所有 key 值:
? interface Person { name: string; age: number; } ? type K1 = keyof Person;
3.in
in 用來遍歷枚舉類型:
? type Keys = "a" | "b" | "c" ? type Obj = { [p in Keys]: any }
4.infer
在條件類型語句中,可以用 infer 聲明一個類型變量并且對它進行使用,。
? type ReturnType<T> = T extends ( ...args: any[] ) => infer R ? R : any;
以上代碼中 infer R 就是聲明一個變量來承載傳入函數(shù)簽名的返回值類型,,簡單說就是用它取到函數(shù)返回值的類型方便之后使用。
5.extends
有時候我們定義的泛型不想過于靈活或者說想繼承某些類等,,可以通過 extends 關(guān)鍵字添加泛型約束,。
? interface ILengthwise { length: number; } ? function loggingIdentity<T extends ILengthwise>(arg: T): T { console.log(arg.length); return arg; }
現(xiàn)在這個泛型函數(shù)被定義了約束,因此它不再是適用于任意類型:
loggingIdentity(3);
這時我們需要傳入符合約束類型的值,,必須包含必須的屬性:
loggingIdentity({length: 10, value: 3});
6.Partial
Partial 的作用就是將某個類型里的屬性全部變?yōu)榭蛇x項 ? ,。
定義:
?
在以上代碼中,首先通過 keyof T 拿到 T 的所有屬性名,,然后使用 in 進行遍歷,,將值賦給 P ,最后通過 T[P] 取得相應(yīng)的屬性值,。中間的 ? 號,,用于將所有屬性變?yōu)榭蛇x。
示例:
? interface Todo { title: string; description: string; } ? function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) { return { ...todo, ...fieldsToUpdate }; } ? const todo1 = { title: "organize desk", description: "clear clutter", }; ? const todo2 = updateTodo(todo1, { description: "throw out trash", });
在上面的 updateTodo 方法中,,我們利用 Partial 工具類型,,定義 fieldsToUpdate 的類型為 Partial ,,即:
{ title?: string | undefined; description?: string | undefined;}
十三、TypeScript 裝飾器
13.1 裝飾器是什么
-
它是一個表達式
-
該表達式被執(zhí)行后,,返回一個函數(shù)
-
函數(shù)的入?yún)⒎謩e為 target,、name 和 descriptor
-
執(zhí)行該函數(shù)后,可能返回 descriptor 對象,,用于配置 target 對象
13.2 裝飾器的分類
13.3 類裝飾器
類裝飾器聲明:
? declare type ClassDecorator = <TFunction extends Function>( target: TFunction ) => TFunction | void;
類裝飾器顧名思義,,就是用來裝飾類的。它接收一個參數(shù):
看完第一眼后,,是不是感覺都不好了,。沒事,我們馬上來個例子:
? function Greeter(target: Function): void { target.prototype.greet = function (): void { console.log("Hello Semlinker!"); }; } ? @Greeter class Greeting { constructor() {
上面的例子中,,我們定義了 Greeter 類裝飾器,同時我們使用了 @Greeter 語法糖,,來使用裝飾器,。
友情提示:讀者可以直接復制上面的代碼,在 TypeScript Playground 中運行查看結(jié)果,。
有的讀者可能想問,,例子中總是輸出 Hello Semlinker! ,能自定義輸出的問候語么 ,?這個問題很好,,答案是可以的。
具體實現(xiàn)如下:
? function Greeter(greeting: string) { return function (target: Function) { target.prototype.greet = function (): void { console.log(greeting); }; }; } ? @Greeter("Hello TS!") class Greeting { constructor() {
13.4 屬性裝飾器
屬性裝飾器聲明:
? declare type PropertyDecorator = (target:Object, propertyKey: string | symbol ) => void;
屬性裝飾器顧名思義,,用來裝飾類的屬性,。它接收兩個參數(shù):
趁熱打鐵,馬上來個例子熱熱身:
? function logProperty(target: any, key: string) { delete target[key]; ? const backingField = "_" + key; ? Object.defineProperty(target, backingField, { writable: true, enumerable: true, configurable: true }); ?
以上代碼我們定義了一個 logProperty 函數(shù),,來跟蹤用戶對屬性的操作,,當代碼成功運行后,在控制臺會輸出以下結(jié)果:
Set: name => semlinkerSet: name => kakuqo
13.5 方法裝飾器
方法裝飾器聲明:
? declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol, descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;
方法裝飾器顧名思義,,用來裝飾類的方法,。它接收三個參數(shù):
廢話不多說,直接上例子:
? function LogOutput(tarage: Function, key: string, descriptor: any) { let originalMethod = descriptor.value; let newMethod = function(...args: any[]): any { let result: any = originalMethod.apply(this, args); if(!this.loggedOutput) { this.loggedOutput = new Array<any>(); } this.loggedOutput.push({ method: key, parameters: args, output: result, timestamp: new Date() }); return result; }; descriptor.value = newMethod; } ? class Calculator { @LogOutput double (num: number): number { return num * 2; } } ? let calc = new Calculator(); calc.double(11);
下面我們來介紹一下參數(shù)裝飾器,。
13.6 參數(shù)裝飾器
參數(shù)裝飾器聲明:
? declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number ) => void
參數(shù)裝飾器顧名思義,,是用來裝飾函數(shù)參數(shù),它接收三個參數(shù):
function Log(target: Function, key: string, parameterIndex: number) { let functionLogged = key || target.prototype.constructor.name; console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has been decorated`);}class Greeter { greeting: string; constructor(@Log phrase: string) { this.greeting = phrase; }}// console output: The parameter in position 0 // at Greeter has been decorated
介紹完 TypeScript 入門相關(guān)的基礎(chǔ)知識,,猜測很多剛?cè)腴T的小伙伴已有 “從入門到放棄” 的想法,,最后我們來簡單介紹一下編譯上下文。
十四,、編譯上下文
14.1 tsconfig.json 的作用
-
用于標識 TypeScript 項目的根路徑,;
-
用于配置 TypeScript 編譯器;
-
用于指定編譯的文件。
14.2 tsconfig.json 重要字段
-
files - 設(shè)置要編譯的文件的名稱,;
-
include - 設(shè)置需要進行編譯的文件,,支持路徑模式匹配;
-
exclude - 設(shè)置無需進行編譯的文件,,支持路徑模式匹配,;
-
compilerOptions - 設(shè)置與編譯流程相關(guān)的選項。
14.3 compilerOptions 選項
compilerOptions 支持很多選項,,常見的有 baseUrl ,、 target 、baseUrl ,、 moduleResolution 和 lib 等,。
compilerOptions 每個選項的詳細說明如下:
? { "compilerOptions": { ?
總結(jié)
對了,,小編為大家準備了一套2020最新的web前端資料,需要點擊下方鏈接獲取方式
學習前端,,你掌握這些,。二線也能輕松拿8K以上
|