好多大廠都在使用 TypeScript,總感受這玩意不看看都不配作前端了😂,看完文檔後就梳理了一下。本文來自個人博客這個想法不必定對系列,so,這個想法不必定對😉
TypeScript 具備類型系統,且是 JavaScript 的超集。它能夠編譯成普通的 JavaScript 代碼。TypeScript 支持任意瀏覽器,任意環境,任意系統而且是開源的。
做爲弱類型、動態型語言,JavaScript 就像未馴化的野馬同樣。每一個人都能上去坐兩下,可是真正可以駕馭的只能是個中好手。前端
近幾年,前端經歷了快速的發展已經再也不是之前隨便玩玩的小玩意了。面對愈來愈大型、愈來愈持久的項目來講,這種寬鬆的方式反而成了阻礙。編程
東西作大了,隨之而來的就是各類規矩
規矩是從經驗中總結,同時也是爲了朝更好的方向發展,就好比編程裏的設計原則和設計模式。「Man maketh manners」,記得王牌特工裏,主角們在教育別人的時候總喜歡說這麼一句話,「不知禮,無以立也」。設計模式
在 TypeScript 裏,「禮」就是 Type,Type 就是規矩。Typescript 經過類型註解提供編譯時的靜態類型檢查,提早發現錯誤,同時也提升了代碼的可讀性和可維護性。數組
TypeScript 裏的類型註解是一種輕量級的爲函數或變量添加約束的方式
在 JavaScript 裏,變量用於在特定時間存儲特定值,其值及數據類型能夠在腳本的生命週期內改變。瀏覽器
而在 TypeScript 中,標識符(變量、函數、類、屬性的名字,或者函數參數)在其定義時就指定了類型(或類型推論出)。在編譯階段,若出現了指望以外的類型,TypeScript 將會提示拋錯(雖然有時候並不會影響程序的正常運行)。dom
在 TypeScript 中,經過 : 類型
的方式爲標識符添加類型註解。函數
let isDone: boolean = false; // boolean; let decLiteral: number = 6; // number; let name: string = "bob"; // string; let list: number[] = [1, 2, 3]; // Array<number>; let list: Array<number> = [1, 2, 3]; // Array<number>; let x: [string, number]; // tuple; enum Color {Red, Green, Blue} // enum; let notSure: any = 4; // any; function warnUser(): void { // void; console.log("This is my warning message"); } let u: undefined = undefined; // undefined; let n: null = null; // null; function error(message: string): never { // never; throw new Error(message); } let obj: object = {}; // object
在 TypeScript 中,數組(Array)是合併了相同類型的對象,而元組(tuple)合併了不一樣類型的對象。(Array<any>
,也能夠合併不一樣類型的數據)學習
類型註解中的類型就是以上的那些類型麼?
TypeScript 的核心原則之一是對值所具備的結構進行類型檢查,它有時被稱作「鴨式辨型法」或「結構性子類型化」。上面的只是基礎類型,它們是填充結構的基本單位而已。在 TypeScript 裏,類型不該該還停留在 JavaScript 數據類型的層面上,還應包括基礎類型的組合結構化。this
let str: 'Hello'; // 字符串字面量類型; str = 'Hi' // error; let something: 'Hello' | 1; // 聯合類型; something = 1 // ok; let obj: {name: string, age: number}; // 對象字面量 obj = { name: "夜曉宸", age: 18, }
換句話說,在定義標識符的時候,用一個類型模板來描述標識符的結構和內部類型組成。即類型模板就是標識符指望的樣子。prototype
代碼是給人看的,順即是給機器運行的
都說好的代碼就該這樣。可是在 TypeScript 裏,這兩句話能夠顛倒下順序。代碼是給機器運行的,順即是給人看的。
在談到 TypeScript 的好處時,有一條很重要,加強了編譯器和 IDE 的功能,包括代碼補全、接口提示、跳轉到定義、重構等。
而這些也得益於標識符的類型的精確劃分或表述,因此想寫好 Typescript 代碼,就應該精確描述標識符的類型,而不是隨處安放的 any
。
表述複雜結構最經常使用的方式 ———— 接口
接口是 JavaScript 中沒有的東西,是一個很是靈活的概念,能夠抽象行爲,也能夠描述「對象的形狀」。
對於須要複用的結構類型,就可使用接口的方式,而不是對象字面量內聯式註解。
interface Iperson { // 對象 name: string, age: number, sayHi(): void, } let obj: Iperson = { name: "夜曉宸", age: 18, sayHi: ()=> {} } /* ——————人工分割線—————— */ interface Iperson { // 函數類型 (name: string, age: number): string } let person: Iperson = (name, age) => { return `${name},${age}` } person('夜曉宸', 18); /* ——————人工分割線—————— */ interface Iperson { // 構造函數 new (name: string, age: number) } let person: Iperson = class Person { name: string; age: number; constructor(name, age) { this.name = name; this.age = age; } } new person('夜曉宸', 18); /* ——————人工分割線—————— */ interface Iperson { // 類實現接口 name: string, age: number, } class Person implements Iperson{ name = '夜曉宸' age = 18 } new Person() /* ——————人工分割線—————— */ interface Iperson { // 混合類型 (name, age): string, age: number, } function Person(): Iperson { let me = <Iperson>function (name, age): string { return `${name}, ${age}` } me.age = 18; return me; } let person = Person(); person('夜曉宸', 18) person.age
以上是接口在對象、普通函數、構造函數、類上的表現。對於接口的屬性,還能夠作到精確控制,如可選屬性、任意屬性、只讀屬性等。
最後,接口間能夠繼承,接口還能夠繼承類。當接口繼承類時,它會繼承類的成員但不包括其實現,可是若繼承了擁有私有或受保護的成員類時,這個接口只能由這個類或其子類來實現了,這個和類的訪問修飾符的特色有關係。
說完接口,就要說說類了,由於它們有多類似的地方,好比充當對象的類型模板,繼承成員等。
類究竟是什麼呢?
ES6 引入了 Class(類)這個概念,經過 class 關鍵字,能夠定義類, Class 實質上是 JavaScript 現有的基於原型的繼承的語法糖. Class 能夠經過extends關鍵字實現繼承。TypeScript 除了實現了全部 ES6 中的類的功能之外,還添加了一些新的用法。
class Person { static age: number = 18; constructor(public name: string, public age: number) { } sayHi(name: string): string{ return `Hi,${name}` } } /* —————— 分割線 —————— */ var Person = /** @class */ (function () { function Person(name, age) { this.name = name; this.age = age; } Person.prototype.sayHi = function (name) { return "Hi," + name; }; Person.age = 18; return Person; }());
TypeScript 編譯後,能夠看出來,類其實就是一個函數而已。
在 ES6 以前,經過構造函數的方式 new
出對象,造出的對象擁有和共享了構造函數內部綁定的屬性方法及原型上的屬性方法。TypeScript 裏的接口描述的類類型就是類的實例部分應該遵循的類型模板。做爲類的靜態部分 ———— 構造函數,函數也應該有本身的屬性特徵。
interface static_person { age: number, new (name: string, age: number); } interface instance_person { name: string, age: number, say(name: string): string } let person: static_person = class Person implements instance_person{ static age: number = 18; constructor(public name: string, public age: number) { } say(name) { return `Hi,${name}` } } new person('夜曉宸',18)
由以上代碼能夠看出,類的靜態部分和動態部分都有各自的類型模板。如果想要將類自身做爲類型模板又該如何作呢?最簡單的方法就是 typeof 類
的方式。
class Person { static age: number = 18; constructor(public name: string, public age: number) {} say(name) { return `Hi,${name}` } } class Man { static age: number; constructor(public name: string, public age: number) {} public sex = 'man'; say(name){return `Hi, ${this.sex},${name}`} } let man: typeof Person = Man; new man('夜曉宸', 18)
類靜態部分、類實例部分和類自身,它們都有本身須要遵循的類型模板。知道了其中的區別,也就能更好得理解類做爲接口使用、接口繼承類等用法了。
class Person { name: string; age: number; } interface Man extends Person { sex: 'man' } let man: Man = { name: '夜曉宸', age: 18, sex: 'man' }
除告終構上的約束,類也經過訪問修飾符對其成員作了約束,包括 public,private,protected,readonly等。
class Person { private name: string; protected age: number; } interface SayPerson extends Person { sayHi(): string } class Human extends Person implements SayPerson { sayHi() { return `Hi, ${this.age}` } }
知道了訪問修飾符的特色,也就明白以前說過的「當接口繼承類時,它會繼承類的成員但不包括其實現,可是若繼承了擁有私有或受保護的成員類時,這個接口只能由這個類或其子類來實現了」。
若是一個標識符的類型不肯定,該如何?
對於一個內部邏輯相差不大,入參類型不一樣的函數來講,不必由於參數類型不一樣而重複大部分代碼,這時就須要一個類型變量來代替。
/* 範型函數 */ class Person { className = 'person' } class Human { classname = 'human' } function create<T>(Class: new () => T) : T{ return new Class(); } create(Person).className /* 範型接口 */ interface Creat<T>{ (Class: new () => T):T } class Person { className = 'person' } class Human { classname = 'human' } function create<T>(Class: new () => T) : T{ return new Class(); } let person: Creat<Person> = create; person(Person) // OK person(Human) // Error
注意了,類型變量表示的是類型,而不是值。類型變量裏塞的多是任意一個類型,但根據場景,咱們最好可以更加精確的描述標識符的類型。應了上面的一句話,「想寫好 Typescript 代碼,就應該精確描述標識符的類型,而不是隨處安放的 any
」。因此對於泛型,咱們也能夠作些約束,即,泛型約束。
class Person { name: string; age: number; } interface Man extends Person { sex: 'man' } function getProperty<T, K extends keyof T>(obj: T, key: K): any { return obj[key] } let man: Man = { name: '夜曉宸', age: 18, sex: 'man' } getProperty(man, 'sex')
用類型變量來註釋標識符的類型有時會以爲仍是不夠精確。
知道標識符的可能類型,而後組合起來
class Man { name: string; age: number; study():string {return ''} } class Women { name: string; age: number; sing():string{return ''} } function instance(Class: Man | Women) { if ((<Man>Class).study) { return (<Man>Class).study() } else { return (<Women>Class).sing() } } let man:Man = { name: '夜曉宸', age: 18, study() { return '我愛學習'; } } let women: Women = { name: 'godness', age: 17, sing() { return '我愛唱歌' } } instance(man) // 我愛學習 instance(women) // 我愛唱歌
有交叉類型、聯合類型等,而類型命名則是更靈活的類型組織方式。
// 官網🌰 type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name { if (typeof n === 'string') { return n; } else { return n(); } }
類型多了以後,有時候須要對某一類型作特別處理,因而有類型斷言 (<類型>
) 和類型守衛(typeof
, instanceof
, in
等)。
還能夠經過條件判斷來選擇哪一種類型。
// 官網🌰 declare function f<T extends boolean>(x: T): T extends true ? string : number; // Type is 'string | number let x = f(Math.random() < 0.5)
固然了,以上代碼好多的標識符是沒有必要添加類型註解的。
類型推斷,即,類型是在哪裏如何被推斷的
類型註解也不是越多越好,即便有些地方你不添加類型註解,TypeScript 也會經過上下文歸類等方式找到最佳通用類型。