【文末送書】Typescript 使用日志
Typescript 使用日志
最近這兩年,有很多人都在討論 Typescript,無論是社區(qū)還是各種文章都能看出來,整體來說正面的信息是大于負面的,這篇文章就來整理一下我所了解的 Typescript。
本文主要分為 3 個部分:
?Typescript 基本概念?Typescript 高級用法?Typescript 總結(jié)
Typescript 基本概念
至于官網(wǎng)的定義,這里就不多做解釋了,大家可以去官網(wǎng)查看。Typescript 設(shè)計目標[1]
我理解的定義:賦予 Javascript 類型的概念,讓代碼可以在運行前就能發(fā)現(xiàn)問題。
Typescript 都有哪些類型
1、Typescript 基本類型,也就是可以被直接使用的單一類型。
?數(shù)字?字符串?布爾類型?null?undefined?any?unknown?void?object?枚舉?never
2、復合類型,包含多個單一類型的類型。
?數(shù)組類型?元組類型?字面量類型?接口類型
3、如果一個類型不能滿足要求怎么辦?
?可空類型,默認任何類型都可以被賦值成 null 或 undefined。?聯(lián)合類型,不確定類型是哪個,但能提供幾種選擇,如:type1 | type2。?交叉類型,必須滿足多個類型的組合,如:type1 & type2。
類型都在哪里使用
在 Typescript 中,類型通常在以下幾種情況下使用。
?變量中使用?類中使用?接口中使用?函數(shù)中使用
類型在變量中使用
在變量中使用時,直接在變量后面加上類型即可。
let a: number;let b: string;let c: null;let d: undefined;let e: boolean;let obj: Ixxx = {a: 1,b: 2,};let fun: Iyyy = () => {};
類型在類中使用
在類中使用方式和在變量中類似,只是提供了一些專門為類設(shè)計的靜態(tài)屬性、靜態(tài)方法、成員屬性、構(gòu)造函數(shù)中的類型等。
class Greeter {static name:string = 'Greeter'static log(){console.log(‘log')}greeting: string;constructor(message: string) {this.greeting = message;}greet() {return "Hello, " + this.greeting;}}let greeter = new Greeter("world");
類型在接口中使用
在接口中使用也比較簡單,可以理解為組合多個單一類型。
interface IData {name: string;age: number;func: (s: string) => void;}
類型在函數(shù)中使用
在函數(shù)中使用類型時,主要用于處理函數(shù)參數(shù)、函數(shù)返回值。
// 函數(shù)參數(shù)function a(all: string) {}// 函數(shù)返回值function a(a: string): string {}// 可選參數(shù)function a(a: number, b?: number) {}
Typescript 高級用法
Typescript 中的基本用法非常簡單,有 js 基礎(chǔ)的同學很快就能上手,接下來我們分析一下 Typescript 中更高級的用法,以完成更精密的類型檢查。
類中的高級用法
在類中的高級用法主要有以下幾點:
?繼承?存儲器 get set?readonly 修飾符?公有,私有,受保護的修飾符?抽象類 abstract
繼承和存儲器和 ES6 里的功能是一致的,這里就不多說了,主要說一下類的修飾符和抽象類。
類中的修飾符是體現(xiàn)面向?qū)ο蠓庋b性的主要手段,類中的屬性和方法在被不同修飾符修飾之后,就有了不同權(quán)限的劃分,例如:
?public 表示在當前類、子類、實例中都能訪問。?protected 表示只能在當前類、子類中訪問。?private 表示只能在當前類訪問。
class Animal {// 公有,私有,受保護的修飾符protected AnimalName: string;readonly age: number;static type: string;private _age: number;// 屬性存儲器get age(): number {return this._age;}set age(age: number) {this._age = age;}run() {console.log("run", this.AnimalName, this.age);}constructor(theName: string) {this.AnimalName = theName;}}Animal.type = "2"; // 靜態(tài)屬性const dog = new Animal("dog");dog.age = 2; // 給 readonly 屬性賦值會報錯dog.AnimalName; // 實例中訪問 protected 報錯dog.run; // 正常
在類中的繼承也十分簡單,和 ES6 的語法是一樣的。
class Cat extends Animal {dump() {console.log(this.AnimalName);}}let cat = new Cat("catname");cat.AnimalName; // 受保護的對象,報錯cat.run; // 正常cat.age = 2; // 正常
在面向?qū)ο笾?,有一個比較重要的概念就是抽象類,抽象類用于類的抽象,可以定義一些類的公共屬性、公共方法,讓繼承的子類去實現(xiàn),也可以自己實現(xiàn)。
抽象類有以下兩個特點。
?抽象類不能直接實例化?抽象類中的抽象屬性和方法,必須被子類實現(xiàn)
tip 經(jīng)典問題:抽象類的接口的區(qū)別
?抽象類要被子類繼承,接口要被類實現(xiàn)。?在 ts 中使用 extends 去繼承一個抽象類。?在 ts 中使用 implements 去實現(xiàn)一個接口。?接口只能做方法聲明,抽象類中可以作方法聲明,也可以做方法實現(xiàn)。?抽象類是有規(guī)律的,抽離的是一個類別的公共部分,而接口只是對相同屬性和方法的抽象,屬性和方法可以無任何關(guān)聯(lián)。
抽象類的用法如下。
abstract class Animal {abstract makeSound(): void;// 直接定義方法實例move(): void {console.log("roaming the earch...");}}class Cat extends Animal {makeSound() {} // 必須實現(xiàn)的抽象方法move() {console.log('move');}}new Cat3();
接口中的高級用法
接口中的高級用法主要有以下幾點:
?繼承?可選屬性?只讀屬性?索引類型:字符串和數(shù)字?函數(shù)類型接口?給類添加類型,構(gòu)造函數(shù)類型
接口中除了可以定義常規(guī)屬性之外,還可以定義可選屬性、索引類型等。
interface Ia {a: string;b?: string; // 可選屬性readonly c: number; // 只讀屬性[key: number]: string; // 索引類型}// 接口繼承interface Ib extends Ia {age: number;}let test1: Ia = {a: "",c: 2,age: 1,};test1.c = 2; // 報錯,只讀屬性const item0 = test1[0]; // 索引類型
接口中同時也支持定義函數(shù)類型、構(gòu)造函數(shù)類型。
// 接口定義函數(shù)類型interface SearchFunc {(source: string, subString: string): boolean;}let mySearch: SearchFunc = function (x: string, y: string) {return false;};// 接口中編寫類的構(gòu)造函數(shù)類型檢查interface IClass {new (hour: number, minute: number);}let test2: IClass = class {constructor(x: number, y: number) {}};
函數(shù)中的高級用法
函數(shù)中的高級用法主要有以下幾點:
?函數(shù)重載?this 類型
函數(shù)重載
函數(shù)重載指的是一個函數(shù)可以根據(jù)不同的入?yún)⑵ヅ鋵?yīng)的類型。
例如:案例中的?doSomeThing?在傳一個參數(shù)的時候被提示為?number?類型,傳兩個參數(shù)的話,第一個參數(shù)就必須是?string?類型。
// 函數(shù)重載function doSomeThing(x: string, y: number): string;function doSomeThing(x: number): string;function doSomeThing(x): any {}let result = doSomeThing(0);let result1 = doSomeThing("", 2);
This 類型
我們都知道,Javascript 中的 this 只有在運行的時候,才能夠判斷,所以對于 Typescript 來說是很難做靜態(tài)判斷的,對此 Typescript 給我們提供了手動綁定 this 類型,讓我們能夠在明確 this 的情況下,給到靜態(tài)的類型提示。
其實在 Javascript 中的 this,就只有這五種情況:
?對象調(diào)用,指向調(diào)用的對象?全局函數(shù)調(diào)用,指向 window 對象?call apply 調(diào)用,指向綁定的對象?dom.addEventListener 調(diào)用,指向 dom?箭頭函數(shù)中的 this ,指向綁定時的上下文
// 全局函數(shù)調(diào)用 - windowfunction doSomeThing() {return this;}const result2 = doSomeThing();// 對象調(diào)用 - 對象interface IObj {age: number;// 手動指定 this 類型doSomeThing(this: IObj): IObj;doSomeThing2(): Function;}const obj: IObj = {age: 12,doSomeThing: function () {return this;},doSomeThing2: () => {console.log(this);},};const result3 = obj.doSomeThing();let globalDoSomeThing = obj.doSomeThing;globalDoSomeThing(); // 這樣會報錯,因為我們只允許在對象中調(diào)用// call apply 綁定對應(yīng)的對象function fn() {console.log(this);}fn.bind(document)();// dom.addEventListenerdocument.body.addEventListener("click", function () {console.log(this); // body});
泛型
泛型表示的是一個類型在定義時并不確定,需要在調(diào)用的時候才能確定的類型,主要包含以下幾個知識點:
?泛型函數(shù)?泛型類?泛型約束 T extends XXX
我們試想一下,如果一個函數(shù),把傳入的參數(shù)直接輸出,我們怎么去給它編寫類型?傳入的參數(shù)可以是任何類型,難道我們需要把每個類型都寫一遍?
?使用函數(shù)重載,得把每個類型都寫一遍,不適合。?泛型,用一個類型占位 T 去代替,在使用時指定對應(yīng)的類型即可。
// 使用泛型function doSomeThing(param: T): T { return param;}let y = doSomeThing(1);// 泛型類class MyClass{ log(msg: T) {return msg;}}let my = new MyClass(); my.log("");// 泛型約束,可以規(guī)定最終執(zhí)行時,只能是哪些類型function d2(param: T): T { return param;}let z = d2(true);
其實泛型本來很簡單,但許多初學 Typescript 的同學覺得泛型很難,其實是因為泛型可以結(jié)合索引查詢符?keyof、索引訪問符?T[k]?等寫出難以閱讀的代碼,我們來看一下。
// 以下四種方法,表達的含義是一致的,都是把對象中的某一個屬性的 value 取出來,組成一個數(shù)組function showKey1(items: K[], obj: T): T[K][] { return items.map((item) => obj[item]);}function showKey2(items: K[], obj: T): Array { return items.map((item) => obj[item]);}function showKey3( items: K[],obj: { [K in keyof T]: any }): T[K][] {return items.map((item) => obj[item]);}function showKey4( items: K[],obj: { [K in keyof T]: any }): Array{ return items.map((item) => obj[item]);}let obj22 = showKey4<"age", { name: string; age: number }>(["age"], {name: "yhl",age: 12,});
類型兼容性
類型兼容性是我認為 Typescript 中最難理解的一個部分,我們來分析一下。
?對象中的兼容?函數(shù)返回值兼容?函數(shù)參數(shù)列表兼容?函數(shù)參數(shù)結(jié)構(gòu)兼容?類中的兼容?泛型中的兼容
在 Typescript 中是通過結(jié)構(gòu)體來判斷兼容性的,如果兩個的結(jié)構(gòu)體一致,就直接兼容了,但如果不一致,Typescript 給我們提供了一下兩種兼容方式:
以?A = B?這個表達式為例:
?協(xié)變,表示 B 的結(jié)構(gòu)體必須包含 A 中的所有結(jié)構(gòu),即:B 中的屬性可以比 A 多,但不能少。?逆變,和協(xié)變相反,即:B 中的所有屬性都在 A 中能找到,可以比 A 的少。?雙向協(xié)變,即沒有規(guī)則,B 中的屬性可以比 A 多,也可以比 A 少。
對象中的兼容
對象中的兼容,采用的是協(xié)變。
let obj1 = {a: 1,b: "b",c: true,};let obj2 = {a: 1,};obj2 = obj1;obj1 = obj2; // 報錯,因為 obj2 屬性不夠
函數(shù)返回值兼容
函數(shù)返回值中的兼容,采用的是協(xié)變。
let fun1 = function (): { a: number; b: string } {return { a: 1, b: "" };};let fun2 = function (): { a: number } {return { a: 1 };};fun1 = fun2; // 報錯,fun2 中沒有 b 參數(shù)fun2 = fun1;
函數(shù)參數(shù)個數(shù)兼容
函數(shù)參數(shù)個數(shù)的兼容,采用的是逆變。
// 如果函數(shù)中的所有參數(shù),都可以在賦值目標中找到,就能賦值let fun1 = function (a: number, b: string) {};let fun2 = function (a: number) {};fun1 = fun2;fun2 = fun1; // 報錯, fun1 中的 b 參數(shù)不能再 fun2 中找到
函數(shù)參數(shù)兼容
函數(shù)參數(shù)兼容,采用的是雙向協(xié)變。
let fn1 = (a: { name: string; age: number }) => {console.log("使用 name 和 age");};let fn2 = (a: { name: string }) => {console.log("使用 name");};fn2 = fn1; // 正常fn1 = fn2; // 正常
理解函數(shù)參數(shù)雙向協(xié)變
1、我們思考一下,一個函數(shù)?dog => dog,它的子函數(shù)是什么?
注意:原函數(shù)如果被修改成了另一個函數(shù),但他的類型是不會改變的,ts 還是會按照原函數(shù)的類型去做類型檢查!
?grayDog => grayDog
? ? ?不對,如果傳了其他類型的 dog,沒有 grayDog 的方法,會報錯。
? ? ?grayDog => animal
? ?同上。
? ? ?animal => animal
? ?返回值不對,返回值始終是協(xié)變的,必須多傳。
? ? ?animal => grayDog
? ?正確。
所以,函數(shù)參數(shù)類型應(yīng)該是逆變的。
2、為什么 Typescript 中的函數(shù)參數(shù)也是協(xié)變呢?
enum EventType { Mouse, Keyboard }interface Event { timestamp: number; }interface MouseEvent extends Event { x: number; y: number }function listenEvent(eventType: EventType, handler: (n: Event) => void) {/* ... */}listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + "," + e.y));
上面代碼中,我們在調(diào)用時傳的是 mouse 類型,所以在回調(diào)函數(shù)中,我們是知道返回的參數(shù)一定是一個 MouseEvent 類型,這樣是符合邏輯的,但由于 MouseEvent 類型的屬性是多于 Event 類型的,所以說 Typescript 的參數(shù)類型也是支持協(xié)變的。
類中的兼容
類中的兼容,是在比較兩個實例中的結(jié)構(gòu)體,是一種協(xié)變。
class Student1 {name: string;// private weight:number}class Student2 {// extends Student1name: string;age: number;}let student1 = new Student1();let student2 = new Student2();student1 = student2;student2 = student1; // 報錯,student1 沒有 age 參數(shù)
需要注意的是,實例中的屬性和方法會受到類中修飾符的影響,如果是 private 修飾符,那么必須保證兩者之間的 private 修飾的屬性來自同一對象。如上文中如果把 private 注釋放開的話,只能通過繼承去實現(xiàn)兼容。
泛型中的兼容
泛型中的兼容,如果沒有用到 T,則兩個泛型也是兼容的。
interface Empty{} let x1: Empty; let y1: Empty; x1 = y1;y1 = x1;
高級類型
Typescript 中的高級類型包括:交叉類型、聯(lián)合類型、字面量類型、索引類型、映射類型等,這里我們主要討論一下
?聯(lián)合類型?映射類型
聯(lián)合類型
聯(lián)合類型是指一個對象可能是多個類型中的一個,如:let a :number | string?表示 a 要么是 number 類型,要么是 string 類型。
那么問題來了,我們怎么去確定運行時到底是什么類型?
答:類型保護。類型保護是針對于聯(lián)合類型,讓我們能夠通過邏輯判斷,確定最終的類型,是來自聯(lián)合類型中的哪個類型。
判斷聯(lián)合類型的方法很多:
?typeof?instanceof?in?字面量保護,===、!===、==、!=?自定義類型保護,通過判斷是否有某個屬性等
// 自定義類型保護function isFish(pet: Fish | Bird): pet is Fish {return (pet).swim !== undefined; }if (isFish(pet)) {pet.swim();} else {pet.fly();}
映射類型
映射類型表示可以對某一個類型進行操作,產(chǎn)生出另一個符合我們要求的類型:
?ReadOnly,將 T 中的類型都變?yōu)橹蛔x。?Partial,將 T 中的類型都變?yōu)榭蛇x。?Exclude,從 T 中剔除可以賦值給 U 的類型。?Extract,提取 T 中可以賦值給 U 的類型。?NonNullable,從 T 中剔除 null 和 undefined。?ReturnType,獲取函數(shù)返回值類型。?InstanceType,獲取構(gòu)造函數(shù)類型的實例類型。
我們也可以編寫自定義的映射類型。
//定義toPromise映射type ToPromise= { [K in keyof T]: Promise }; type NumberList = [number, number];type PromiseCoordinate = ToPromise; // [Promise, Promise ]
Typescript 總結(jié)
寫了這么多,接下來說說我對 Typescript 的一些看法。
Typescript 優(yōu)點
1、靜態(tài)類型檢查,提早發(fā)現(xiàn)問題。
2、類型即文檔,便于理解,協(xié)作。
3、類型推導,自動補全,提升開發(fā)效率。
4、出錯時,可以大概率排除類型問題,縮短 bug 解決時間。
實戰(zhàn)中的優(yōu)點:
1、發(fā)現(xiàn) es 規(guī)范中棄用的方法,如:Date.toGMTString。
2、避免了一些不友好的開發(fā)代碼,如:動態(tài)給 obj 添加屬性。
3、vue 使用變量,如果沒有在 data 定義,會直接拋出問題。
Typescript 缺點
1、短期增加開發(fā)成本。
2、部分庫還沒有寫 types 文件。
3、不是完全的超集。
實戰(zhàn)中的問題:
1、還有一些坑不好解決,axios 編寫了攔截器之后,typescript 反映不到 response 中去。
參考資料
?Typescript 官網(wǎng)[2]?深入理解 Typescript[3]
References
[1]?Typescript 設(shè)計目標:?https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals[2]?Typescript 官網(wǎng):?https://www.tslang.cn/[3]?深入理解 Typescript:?https://jkchao.github.io/typescript-book-chinese/
送書
留言點贊最多的兩個人分別獲取?TypeScript 項目開發(fā)實戰(zhàn)一本。
在看隨機抽取一個人獲取TypeScript 項目開發(fā)實戰(zhàn)一本。
?本次活動需要在開獎前關(guān)注《腦洞前端》的人才有資格,抽在看的朋友需要在開獎前添加我的微信 DevelopeEngineer。
?
開獎時間:2020-09-25. 12:00:00
兌獎截止時間:2020-09-27 00:00:00
書籍介紹
《TypeScript項目開發(fā)實戰(zhàn)》是一本TypeScript進階實踐指南,通過9個實用項目,詳細講解如何使用TypeScript和不同的JavaScript框架開發(fā)高質(zhì)量的應(yīng)用程序。書中不僅介紹TypeScript的核心概念與技術(shù),還涵蓋Angular和React的一些新功能,以及GraphQL、微服務(wù)和機器學習等相關(guān)的新技術(shù)。

【全書共10章】:
第1章介紹你之前可能沒有接觸過的TypeScript功能 第2章將編寫第一個實用的項目——一個簡單的markdown編輯器 第3章將使用流行的React庫構(gòu)建一個聯(lián)系人管理器 第4章介紹MEAN棧 第5章介紹如何使用GraphQL和Apollo創(chuàng)建Angular待辦事項應(yīng)用程序 第6章介紹如何使用Socket.IO構(gòu)建一個聊天室應(yīng)用程序 第7章介紹如何使用必應(yīng)地圖和Firebase創(chuàng)建基于云的Angular地圖應(yīng)用程序 第8章介紹如何使用一個等效的基于React的棧 第9章介紹如何使用TensorFlow.js在Web瀏覽器中托管機器學習 第10章介紹如何使用ASP.NET Core和免費的Discogs音樂API來編寫一個音樂庫應(yīng)用程序
【通過閱讀本書,你將學到】:
使用TypeScript和常用模式編寫代碼。 在TypeScript中使用流行的框架和庫。 使用TypeScript來利用服務(wù)器和客戶端的功能。 應(yīng)用令人興奮的新范式,如GraphQL和TensorFlow。 使用流行的、基于云的身份驗證服務(wù)。 結(jié)合TypeScript和C#來創(chuàng)建ASP.NET Core應(yīng)用程序。
【本書面向的讀者】
本書的讀者應(yīng)該至少已經(jīng)熟悉TypeScript的基礎(chǔ)知識。如果你知道如何使用TypeScript編譯器tsc來構(gòu)建配置文件和編譯代碼,也知道TypeScript中的類型安全、函數(shù)和類等基礎(chǔ)知識,那將大有裨益。
即使你對TypeScript有比較深入的了解,本書中也會介紹一些你以前可能沒有使用過的技術(shù),你應(yīng)該會對這些資料感興趣。
推薦閱讀
2、你不知道的 TypeScript 泛型(萬字長文,建議收藏)
3、尤大 3 天前發(fā)在 GitHub 上的 vue-lit 是啥?
如果覺得文章不錯,幫忙點個在看唄
