还不会ts?一文带你打开ts的大门
一文帶你打開ts的大門
- 序言
- 一、什么是TypeScript?
- 1、編程語言的類型
- 2、TypeScript究竟是什么?
- 二、為什么要學習TypeScript?
- 1、程序更容易理解
- 2、效率更高
- 3、更少的錯誤
- 4、非常好的包容性
- 5、一點小缺點
- 三、typescript入門
- 1、如何安裝TypeScript
- 2、查看版本號
- 3、運行ts文件
- 4、創建ts項目
- 四、Typescript數據類型
- 1、原始數據類型和Any類型
- (1)原始數據類型
- (2)Any 類型
- 2、數組和元組
- (1)數組
- (2)元組
- 3、interface 接口
- 4、function函數
- 5、類型推論、聯合類型和類型斷言
- (1)類型推論
- (2)聯合類型
- (3)類型斷言
- 五、Typescript中的類:class
- 1、類的定義
- (1)類(Class)
- (2)對象(Object)
- (3)面向對象(OOP)的三大特性
- 2、Typescript中的類
- 3、類和接口
- (1)解決什么問題
- (2)如何解決
- (3)舉個例子
- 4、抽象類
- (1)抽象類是什么
- (2)舉個例子
- (3)抽象類和接口
- 六、枚舉
- 1、普通枚舉
- 2、常量枚舉
- 七、泛型
- 1、普通泛型
- 2、約束泛型
- 3、泛型在類和接口中的使用
- (1)泛型在類中的使用
- (2)泛型在接口中的使用
- 4、泛型中keyof語法的使用
- 八、類型別名
- 1、類型別名
- 2、字符串字面量
- 3、交叉類型
- 九、命名空間
- 1、namespace是什么
- 2、namespace的好處
- 3、依賴命名空間聲明
- 十、聲明文件
- 1、 .d.ts 文件引入
- 2、使用 interface 語法實現函數重載
- 3、聲明對象
- 4、npm模塊化引入
- 十一、內置類型
- 十二、TypeScript中的配置文件
- 十三、在ts中對代碼進行模塊化組織
- 1、項目結構
- 2、模塊化拆分
- 十四、結束語
- 彩蛋 One More Thing
- (:專欄直通車
- (:番外
序言
眾所周知, js 是一門弱類型語言,并且規范較少。這就很容易導致在項目上線之前我們很難發現到它的錯誤,等到項目一上線,渾然不覺地, bug 就UpUp了。于是,在過去的這兩年, ts 悄悄的崛起了。
周一隨著一波熱潮,也開始進入了 ts 的世界,不得不感嘆 ts 的靜態美。
下面的文章中將講解我對 TS 入門的一些歸納總結。一起來了解一下吧!
一、什么是TypeScript?
1、編程語言的類型
| JavaScript | C,C++,C#,JAVA |
2、TypeScript究竟是什么?
-
Typescript,即 Javascript that scales ;
-
ts 把不看重類型的動態語言 JS 轉變成關注類型的靜態語言;
-
可以說ts是靜態類型風格的類型系統;
-
從 es6 到 es10 甚至是 esnext 的語法支持;
-
兼容各種瀏覽器,各種系統,各種服務器,完全開源。
二、為什么要學習TypeScript?
1、程序更容易理解
動態語言存在函數或者方法中其輸入輸出的參數類型等問題,同時,動態語言還受到各種各樣的約束,比如需要手動調試等等。那么有了 ts ,代碼本身就可以解決上述問題了, ts 讓程序更容易理解,程序理解我們,我們就可以少干很很多事情。
就像我們在與別人交談時,假如我們邏輯很清晰的表達給對方,對方馬上聽懂了,并且理解了我們,我們也很省力,不用長篇大論的介紹。
2、效率更高
ts 可以在在不同的代碼塊和定義中進行跳轉,并且代碼有補全功能。
同時, ts 還有豐富的接口提示,可以通過使用 . 來提示所有的接口內容。
3、更少的錯誤
ts 在編程期間,就可以發現大部分的錯誤。這樣就可以杜絕掉一些比較常見的錯誤,也使得后面程序運行更加通暢。
4、非常好的包容性
ts 可以完全地兼容 Javascript ,同時,如果要引入像 JQuery 之類的第三方庫時,可以單獨編寫類型文件來引入這些庫。
5、一點小缺點
相比于 js 來講, ts 在學習之初,需要去習慣一些規范,短期內會增加一點學習成本。但短期的學習成本增加將會使得在后期的開發當中減少很多不必要的錯誤和麻煩,間接的也為自己的開發帶來很大的益處。
閑談到此結束,讓我們一起來進入 ts 的世界吧!
三、typescript入門
1、如何安裝TypeScript
npm install -g typescript2、查看版本號
tsc -v3、運行ts文件
npm i ts-node@8.4.1 -g ts-node demo.ts4、創建ts項目
npm init -y tsc --init npm i ts-node -D npm i typescript -D四、Typescript數據類型
1、原始數據類型和Any類型
(1)原始數據類型
//定義一個布爾值數據 let isDone: boolean = false//定義一個數字類型 let age: number = 20//定義字符串類型 let firstName: string = 'monday' let message: string = `Hello, ${firstName}`//定義undefind和null類型 let u: undefined = undefined let n: null = null//給數字賦值undefid let num: number = undefined(2)Any 類型
如果我們有時候不能確定一個數據是什么類型的話,那么我們可以用any類型來定義。比如:
//定義一個any類型數據 let notSure: any = 4 notSure = 'maybe a string' notSure = truenotSure.myName notSure.getName()2、數組和元組
(1)數組
//聲明一個數字類型的數組 //注意:后面的數組只能傳遞數字,傳遞其他類型的數據都會報錯 let arrOfNumbers: number[] = [1, 2, 3]; arrOfNumbers.push(3)function test(){//arguments 為類數組console.log(arguments) }(2)元組
//確定一個元組里面的內容和數量,下面表示確定user這個元組必須且只能接收兩個參數 //同時第一個屬性必須是String類型,第二個屬性是Number類型 let user: [String, Number] = ['abc', 13]3、interface 接口
interface的定義:
- 對 對象Object 的形狀 (shape) 進行描述;
- Duck Typing(鴨子類型)。
我們來看一段代碼:
interface Person{// readonly表示只讀狀態readonly id: number,name: String,//加一個問號表示該參數可選可不選age?: number }let monday: Person = {id: 1,name: 'monday',age: 18 }monday.id = 12323; //因為加了readonly,所以此時訪問不了,會報錯4、function函數
function函數是什么:
- 在 JS 中,函數是一等公民。
- 函數和其他類型的對象都一樣,可以作為參數,可以存入數組,也可以被另外一個函數返回,可以被賦值給另外一個變量。
- 函數主要由兩個部分組成:輸入(傳參)和輸出(返回結果)。
我們來看個例子:
function add(x: number, y: number, z?: number): number{if(typeof z === 'number'){return x + y + z;}else{return x + y;} }let result = add(1, 2, 3); console.log(result); //6通過以上函數,我們實現了兩個樹或者三個樹的相加操作。此時,需要我們注意的是,可選參數后面不能再添加不確定參數,否則程序就會發生混亂。比如:
function add(x: number, y: number, z?: number, t: number): number{if(typeof z === 'number'){return x + y + z;}else{return x + y;} }以上代碼中的 t 是肯定不被允許添加的,因為前面已經有了可選參數 z ,而后面又突然健冒出來個 t ,想想都不太合理。
到這里,假設我們有一個新的變量名,名字叫 add2 。這個時候我們想要給它一個像 add 函數一樣的類型。那么該怎么處理呢?
let add2: (x:number, y:number, z?: number) => number = add注意上面這個箭頭 => 不是 ES6 中的箭頭函數,而是 ts 中聲明函數類型返回值的方法。
上面這個語句中就說明了, add2 返回的值是一個 number 類型數值,并且讓它等于 add 函數。同時,要記得的是,在 ts 當中,凡是在 : 后面都是聲明在聲明類型。
上面這樣寫好像有點冗余,我們來用 interface 來實現同樣的效果。
在第3點的 interface 中我們了解到, interface 是對對象的形狀進行描述,但值得注意的是, interface 也可以是對函數的形狀進行描述。我們用代碼來實現一下。
interface ISum {(x: number, y: number, z?: number) : number }let add2: ISum = add通過以上代碼,我們看到,用 interface 來封裝一個函數的返回值來行,看起來優雅了不少。這里先體會一下, interface 的強大之處,在后面還會繼續講解。
5、類型推論、聯合類型和類型斷言
(1)類型推論
有時候我們還沒有給一個數據定義類型,就直接給它賦值了。這個時候我們要怎么來判斷呢。這個數據的類型呢?
比如:
let str = 123當出現這樣的情況時,編譯器會直接給 str 賦上 number 類型。那么此時如果我們想這么干:
let str = 123 str = 'asd' //會報錯結果當然時不行的。當第一次賦值的時候,編譯器就已經給 str 一個 number 類型,認定 str 就是 number 類型。而后我們還想要給 str 賦值上一個 string 類型的數據,肯定是會報錯的。
(2)聯合類型
有時候我們對一個數據的類型不夠確定,比如說不知道某一個數據它是 number 類型還是 string 類型。這個時候我們就可以用聯合類型來進行一波操作。
let numberOrString: number | string通過這種方式,我們對我們所定義的屬性 numberOrString 進行聯合類型操作。
一般情況下,聯合類型會結合類型斷言來進行使用。接下來我們來講類型斷言。
(3)類型斷言
1) 當 TypeScript 不確定一個聯合類型的變量到底是哪個類型的時候,我們只能訪問此聯合類型的所有類型中共有的屬性或方法,而有時候呢,我們確實需要在還不確定類型的時候就訪問其中一個類型特有的屬性或方法。因此我們采用類型斷言的方式將其指定為一個類型。(這么做只是先欺騙了 ts ,讓其信任我們所指定的類型)
let str = 123 function getLength(input: string | number) : number{// 用as對input進行類型斷言,先給input指定一個類型,后面判斷不是再進行轉換//注意:類型斷言只做類型選擇,而不做類型轉換const str = input as stringconsole.log(typeof str)if(str.length){return str.length}else{const number = input as numberreturn number.toString().length} }2) 看到這里,已經開始感覺到類型斷言的神奇之處。但用上面這種方法感覺好像還有一點點冗余,于是我們引入一個 type guard ,即類型守護。我們來看下實現方式。
let str = 123 function getLength2(input: string | number): number{if(typeof input === 'string'){return input.length}else{return input.toString().length} }上面這種方法被稱為是 typeof 語法做類型守護。
3) 繼續,我們來看另外一種做類型守護的方法, in 語法 。具體代碼如下:
interface Bird {fly: Boolean;sing: () => {}; }interface Dog {fly: boolean;bark: () => {}; }function trainAnimal(animal: Bird | Dog) {if('sing' in animal) {animal.sing();}else {animal.bark();} }在上面的 'sing' in animal 中,我們就可以判斷到 animal 是一個 Bird 類型,之后進行 else 操作。上面判斷已經是 Bird ,那下面的 else 他就會去往 Dog 類型找,因此也就可以找到 bark 方法。
五、Typescript中的類:class
在 js 中我們用了構造函數和原型鏈的方式來實現繼承,同時在 ES6 中出現了 class 類繼承的方法。那在 typescript 中呢,繼承的方法又更加豐富了。讓我們一起來一探究竟吧!
1、類的定義
我們先來看下類的定義。
(1)類(Class)
類定義了一切事物的抽象特點,包含它的屬性和方法。比如:
class Animal{// 構造函數是實例化執行時候的邏輯constructor(name){this.name = name}run(){return `${this.name} is running`} }閱讀以上代碼我們可以知道,通過 class 可以定義一個類。
(2)對象(Object)
對象 Object ,就是類的實例。舉個例子: 🙆?♂?
我們可以把類 class 比喻成一張藍圖,比如說汽車是一個 class ,那么它就像是一張造汽車的圖紙。第二個是 Object, Object 通過 new 生成,那么前面有了汽車的藍圖,我們現在就可以創造實實在在的汽車了。我們可以說一輛特斯拉是汽車的實例,也可以說寶馬是汽車的另外一個實例。
同樣我們用上面的例子來做衍生。具體如下:
class Animal{// 構造函數是實例化執行時候的邏輯constructor(name){this.name = name}run(){return `${this.name} is running`} } const snake = new Animal('lily') console.log(snake.run())閱讀以上代碼我們可以知道,我們定義了一個 snake ,這個 snake 繼承了 Animal 類,因此它就可以用 Animal 類的屬性和方法。
此時打印結果如下:
(3)面向對象(OOP)的三大特性
面向對象的三大特性分別為:封裝、繼承、多態 。
- 封裝: 指將數據的操作細節隱藏起來,只暴露對外的接口。那這樣子的話,對于外界的調用端來說,他們不需要也不可能知道細節,只能通過對外的接口來訪問該對象。
- 繼承: 子類可以繼承父類,子類除了擁有父類的所有特征外,還會擁有一些更具體的特性。
- 多態: 由繼承產生的相關不同的類,對同一個方法可以有不同的響應。比如,貓和狗,他們都可以繼承 Animal 類,但是他們分別實現 run() 方法,此時呢,針對某一個實例,我們無需了解它是貓還是狗,這個時候可以直接調用 run() ,程序會自動判斷出來,應該如何去執行這個方法。
同樣,我們用上面的代碼做衍生,來看繼承和多態是怎么樣的。
繼承:
class Animal{// 構造函數是實例化執行時候的邏輯constructor(name){this.name = name}run(){return `${this.name} is running`} } const snake = new Animal('lily') // console.log(snake.run())class Dog extends Animal{bark(){return `${this.name} is barking`} }const xiaoqi = new Dog('xiaoqi') console.log(xiaoqi.run()) console.log(xiaoqi.bark())此時打印結果如下:
從上面可以看到, Dog 繼承了 Animal 類,此時 Dog 就擁有了 Animal 類的屬性和方法。而 xiaoqi 實例化了 Dog ,因此它也擁有 Dog 的屬性和方法。
多態:
class Animal{// 構造函數是實例化執行時候的邏輯constructor(name){this.name = name}run(){return `${this.name} is running`} } const snake = new Animal('lily') // console.log(snake.run()) //----------------------------------- class Dog extends Animal{bark(){return `${this.name} is barking`} }const xiaoqi = new Dog('xiaoqi') console.log(xiaoqi.run()) console.log(xiaoqi.bark()) //----------------------------------- class Cat extends Animal{// 靜態方法不需要進行實例化,直接在類上調用即可static categories = ['mammal']constructor(name){super(name)console.log(this.name)}run(){return `Meow, ` + super.run() } } const maomao = new Cat('maomao') console.log(maomao.run()) // 直接訪問靜態屬性 // 為什么要有靜態屬性?當定義和實例沒有太大關系時,可以考慮使用靜態方法實現 console.log(Cat.categories)此時打印結果如下:
閱讀代碼我們可以發現, xiaoqi 繼承了 dog 的 run() 方法,而 Cat 繼承了 Animal 類,但是它對 run() 方法進行了改寫,因此最終的 run() 方法為改寫后的效果。
所以, maomao 繼承了 Cat 類,最后 maomao 調用 run() 方法時,就會調用 Cat 里面改寫的 run() 方法,而不是 Animal 類的 run() 方法。
這樣, xiaoqi 和 maomao 雖然同樣繼承自 Animal 類,但他們調用 run() 方法的結果各自相互獨立,如此,就實現了多態。
同時,我們還要注意一個點,就是靜態屬性。大家可以看到上面定義的 categories ,用了 static 來定義它為靜態屬性。當把變量定義為靜態屬性時,則當外部需要該靜態方法時,不需要進行實例化,之類在類上調用即可。
那么問題來了,我們什么時候才需要有靜態屬性呢?
其實,當定義的內容和實例沒有太大關系時,就可以考慮使用靜態方法。比如常量的使用,常量基本是固定的,不會變的,所以我們可以考慮直接使用靜態方法來獲取它。
2、Typescript中的類
Typescript是通過什么方式來增強類的呢,typescript一般通過以下四種修飾符來增強類:
| public | 修飾的屬性或方法是公有的 |
| private | 修飾的屬性或方法是私有的 |
| protected | 修飾的屬性或方法是受保護的 |
| readonly | 只能讀不能寫 |
有了以上這四種修飾符呢,我們就可以給類的方法和屬性進行權限管理。為什么要做權限管理呢?因為總有些內容,我們是不愿意暴露給外部使用的,所以需要進行權限管理。
值得注意的是,對于 protected 這個修飾符來說,只有子類可以訪問父類的屬性和方法,其他實例都不能訪問。這其實可以把 protected 這個變量理解為遺產,父類的東西直接給子女繼承,其余外部人員一概不能訪問。
3、類和接口
(1)解決什么問題
繼承存在著這樣一個困境,在面向對象的世界中,一個類只能繼承另外一個類,有時候同類之間有一些共同的特性,但是使用子類來繼承父類又很難完成。于是接口就出現了。
(2)如何解決
類可以使用 implements 來實現接口,怎么做呢?我們可以把這些相同的特性提取成接口,然后用 implements 這個關鍵字來實現,這樣就大大提高了面向對象的靈活性。
(3)舉個例子
假如我們現在要讓一輛汽車和一部手機來實現打開播放器的功能,那么我們會這么實現:
class Car{switchRadio(trigger: boolean){} }class CellPhone{switchRadio(trigger: boolean){} }但是這樣子看起來好像就沒有特別雅觀。于是我們可以寫一個打開播放器的接口,然后用 implements 來實現這個功能。代碼如下:
interface Radio{switchRadio(trigger: boolean): void }class Car implements Radio{switchRadio(trigger: boolean){} }class CellPhone implements Radio{switchRadio(trigger: boolean){} }這樣,就讓 Car 和 CellPhone 實現了打開播放器的功能。
接下來,我們繼續寫一個接口,可以實現檢查電池電量的功能。并且讓手機不僅可以打開播放器,還可以檢查電池電量。代碼如下:
interface Radio{switchRadio(trigger: boolean): void }interface Battery{checkBatteryStatus(): void }class Car implements Radio{switchRadio(trigger: boolean){} }class CellPhone implements Radio,Battery{switchRadio(trigger: boolean){}checkBatteryStatus(){} }閱讀代碼我們可以發現,我們要給繼承兩個接口 Radio,Battery ,這樣看似乎還有點冗余。于是我們可以這樣實現:
interface Radio{switchRadio(trigger: boolean): void }interface RadioWithBattery extends Radio{checkBatteryStatus(): void }class Car implements Radio{switchRadio(trigger: boolean){} }class CellPhone implements RadioWithBattery{switchRadio(trigger: boolean){}checkBatteryStatus(){} }通過 interface 繼承 interface ,最終用 implement 去抽象和驗證類的屬性和方法,達到抽離功能的目的。
相信通過以上的簡單了解,大家能感受到一點 interface 的奇妙之處。
4、抽象類
(1)抽象類是什么
所謂抽象類,就是把共用性的一些東西給抽離出來,也就是把通用性的一些東西做一些封裝。
(2)舉個例子
我們來舉個例子,看下抽象類是怎么使用的。具體代碼如下:
// 抽象類 abstract class Geom {width: number;getType() {return 'demo';}abstract getArea(): number; }class Circle extends Geom{getArea() {return 123;} }class Squre {} class Triangle{}(3)抽象類和接口
有小伙伴可能已經發現,抽象類跟上面我們學到的接口似乎有點相似。那事實上,抽象類是把一些跟類相關的,通用的一些方法給抽象出來。而接口呢,是把各種跟對象和屬性相關的給抽象出來。這是他們兩者的區別,但它們的本質都是去把一些通用性的東西做封裝。
六、枚舉
1、普通枚舉
枚舉常使用于我們在程序中需要做權限管理或者做判斷時等各種場景。枚舉比較簡單,下面直接用代碼演示:
enum Direction{Up,Down,Left,Right }console.log(Direction.Up) //0 console.log(Direction.Down) //1 console.log(Direction.Left) //2 console.log(Direction.Right) //3 console.log(Direction[0]) //Up除了以上基本用法外,我們還可以給枚舉賦值:
enum Direction{Up = 10,Down,Left,Right }console.log(Direction.Up) //102、常量枚舉
我們來定義一個常量,與 enum 做判斷。
enum Direction{Up = 'Up',Down = 'Down',Left = 'Left',Right = 'Right' }//定義一個常量,直接與enum做判斷 const value = 'Up'; if(value === Direction.Up){console.log('go Up!') // go Up! }使用常量枚舉可以有效地提升性能,常量會內聯枚舉的任何用法,而不會把枚舉變成任意的 Javascript 代碼。
這樣一說,那是不是所有的 enum 都可以使用常量枚舉呢?答案自然是否定的。
枚舉的值有兩種類型,一種是常量值枚舉(constant),一種是計算值枚舉(computed)。只有常量值枚舉可以進行常量枚舉,而計算值枚舉不能進行常量枚舉。
七、泛型
接下來我們來講 TypeScript 中最難的一部分,泛型。
1、普通泛型
泛型,即 generics 。指在定義函數、接口或類的時候,我們不預先指定類型,而是在使用的時候再指定類型和其特征。
可以理解為,泛型就相當于一個占位符或者是一個變量,在使用時再動態的填入進去,填進去以后既可以來確定我們的類型值。
接下來我們用代碼來演示一下:
function echo<T>(arg: T): T{return arg }const result = echo(true) console.log(result) // true我們通過 <> 來定義一個未知的泛型,之后當我們給它賦值時,就可以對應值的數據類型。
現在我們再用泛型來定義一個 number 類型的數組。具體代碼如下:
// 早期定義一個number類型的數組 let arr: number[] = [1, 2, 3] // 用泛型定義一個number類型的數組 let arrTwo: Array<number> = [1, 2, 3]假如我們現在要調換兩個元素的位置,那么用泛型我們可以這么實現。具體代碼如下:
function swap<T, U>(tuple: [T, U]): [U, T]{return [tuple[1], tuple[0]] }const result2 = swap(['abc', 123]) console.log(result2[0]) //123 console.log(result2[1]) //abc通過泛型,我們就順利調換了兩個元素的位置。
2、約束泛型
在泛型中使用 extends 關鍵字,就可以讓傳入值滿足我們特定的約束條件,而不是毫無理由的隨意傳入。
比如,我們想要讓我們定義的內容是一個數組,我們可以這么處理。具體代碼如下:
function echoWithArr<T>(arg: T[]): T[]{console.log(arg.length)return arg } const arrs = echoWithArr([1, 2, 3])這樣,就把 arrs 定義為一個數組。
假設我們現在想要讓我們定義的內容可以訪問到 length 方法,那么我們需要加一點佐料。具體代碼如下:
interface IWithLength{length: number }function echoWithLength<T extends IWithLength>(arg: T): T{console.log(arg.length)return arg }const str = echoWithLength('str') const obj = echoWithLength({ length: 10, width: 20 }) const arr2 = echoWithLength([1, 2, 3])通過 extends 關鍵字來繼承特定的 interface ,使得我們定義的內容 str , obj ,arr2 達到可以訪問length方法的目的。
通過以上舉例,我們可以知道,泛型可以用來靈活的約束參數的類型,參數不需要是個特別死板的類型,而可以通過我們的約束來達到我們想要的目的。
3、泛型在類和接口中的使用
(1)泛型在類中的使用
class Queue<T>{private data = []push(item: T){return this.data.push(item)}pop(): T{return this.data.shift()} } // 確定這是一個number類型的隊列 const queue = new Queue<number>() queue.push(1) console.log(queue.pop().toFixed())(2)泛型在接口中的使用
interface KeyPair<T, U>{key: Tvalue: U } let kp1: KeyPair<number, string> = {key: 1, value: 'str'} let kp2: KeyPair<string, number> = {key: 'str', value: 2}通過以上代碼演示可以發現,泛型就像是創建了一個擁有特定類型的容器,就仿佛給一個容器貼上標簽一樣。
4、泛型中keyof語法的使用
interface Person {name: string;age: number;gender: string; }// type T = 'name'; // key: 'name'; // Person['name'];// type T = 'age'; // key: 'age'; // Person['age'];// type T = 'gender'; // key: 'gender'; // Person['gender'];class Teacher {constructor(private info: Person) {}// keyof關鍵字getInfo<T extends keyof Person>(key: T): Person[T] {return this.info[key];} }const teacher = new Teacher({name: 'Monday',age: 18,gender: 'female' });const test = teacher.getInfo('age'); console.log(test);八、類型別名
1、類型別名
類型別名,即 type aliase 。類型別名可以看作是一個快捷方式,可以把一個寫起來很繁瑣的類型創建一個簡單的寫法。比如:
//用以下這種寫法,每次都要寫一長串的(x: number, y: number) => number let sum: (x: number, y: number) => number const result = sum(1, 2)//用type給類型進行別名 type PlusType = (x: number, y: number) => number let sum2: PlusType const result2 = sum2(2, 3)//一個類型可以是字符串也可以是數字 type StrOrNumber = string | number let result3: StrOrNumber = '123' result3 = 1232、字符串字面量
字符串字面量,指可以提供一系列非常方便使用的常量寫法。比如:
const str: 'name' = 'name' const number: 1 = 1 type Direction = 'Up' | 'Down' | 'Left' | 'Right' let toWhere: Direction = 'Left'3、交叉類型
交叉類型,使用 type 這個擴展對象的一種方式。比如:
interface IName{name: string } type IPerson = IName & {age: number} let person: IPerson = {name: 'monday', age: 18}九、命名空間
1、namespace是什么
假設我現在有一個命名空間,那么命名空間里面的內容將不會被暴露出去。如下代碼所示:
namespace Home {class Header {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Header';document.body.appendChild(elem);}}class Content {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Content';document.body.appendChild(elem);}}class Footer {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Footer';document.body.appendChild(elem);}}class Page {constructor() {new Header();new Content();new Footer();}} }在上面的代碼中,有一個命名為 Home 的命名空間,里面一共有 4 個類。那么這個時候,我們想要在外部使用其中的某一個類,是沒有辦法的。
那如果我們想要將命名空間里面的某個類給暴露出去,該怎么處理呢?
通常情況下,我們會在類的前面加上 export 關鍵字。類似下面這樣:
namespace Home {// 加上 export 關鍵字export class Header {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Header';document.body.appendChild(elem);}}class Content {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Content';document.body.appendChild(elem);}}class Footer {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Footer';document.body.appendChild(elem);}}class Page {constructor() {new Header();new Content();new Footer();}} }大家可以看到,加上 export 關鍵字,就可以將該類在外部進行暴露。而沒有加 export 關鍵字的,在外部就依然是訪問不到的。
現在我們來看下它具體如何使用。具體代碼如下:
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8" /><title>Document</title><script src="./dist/page.js"></script> </head><body><script>new Home.Header();</script> </body></html>大家可以看到,通過 new Home.Header(); 的方式,我們可以訪問到命名空間 Home 中暴露出來的 Header 類。而其他沒有加 export 關鍵字的,都是無法正常訪問到的,它讓本來全局的四個變量變得只剩下 Home 一個,這就是命名空間 namespace 給我們帶來的好處。
2、namespace的好處
現在,我們來梳理一下命名空間給我們帶來的好處。namespace 給我們帶來的一個好處就是,用一個類似模塊化的開發方式,讓我們能夠盡可能少的去生成全局變量。或者說,把一組相關的內容封裝在一塊,最終對外提供統一的暴露接口。
3、依賴命名空間聲明
假設我們要在一個命名空間里面去引入另一個命名空間,該怎么使用呢?如下代碼所示:
// Home這個命名空間要去依賴其他命名空間的聲明 ///<reference path='./components.ts' /> namespace Home {export class Page {user: Components.User = {name: 'monday'}constructor() {new Components.Header();new Components.Content();new Components.Footer();}} }我們可以通過 ///<reference path='./components.ts' /> 這種方式,去引入 Components 命名空間,以供我們使用。
十、聲明文件
我們在寫ts時,難免會有遇到要引入第三方庫的時候。這個時候就需要ts來做特殊處理。主要有以下兩種做法:
1、 .d.ts 文件引入
假設我們要引入 JQuery 庫來使用,那么我們可以在外部新增一個 JQuery.d.ts 文件,文件內代碼如下:
// 第一種類型:定義全局變量 declare var JQuery: (selector: string) => any;// 第二種類型:定義全局函數→傳遞函數 declare function $(readFunc: () => void): void;// 第三種類型:定義全局函數→傳遞字符串 interface JQueryInstance {html: (html: string) => {}; } declare function $(selector: string): JQueryInstance;之后便可以在我們定義的 ts 文件下引用 JQuery 相關庫的內容。比如:
$(function() {$('body').html('<div>123</div>'); });2、使用 interface 語法實現函數重載
上面的第二和第三種類型,使用了傳遞函數和傳遞字符串的兩種方式,來實現了函數重載。那么下面,我們來用 interface 語法,來改造一下這種函數重載的方式。 .d.ts 文件代碼如下:
interface JQueryInstance {html: (html: string) => JQueryInstance; }// 使用interface的語法,實現函數重載 interface JQuery {(readFunc: () => void): void;(selector: strring): JQueryInstance; } declare var $: JQuery;大家可以看到,我們通過 interface 的方式,將 readFunc 和 selector 給一起并入 JQuery 接口中,最后我們直接將 JQuery 給暴露出去即可。
3、聲明對象
上面我們遇到的是 $ 只是函數的時候,進行的函數重載。那如果此時的 $ 既要當作是函數使用,又要當作是對象使用呢?假設我們現在有這么一段代碼:
$(function() {$('body').html('<div>123</div>');new $.fn.init(); })現在,我們要在 $ 中去訪問到 fn 和 init ,那這個時候 $ 不僅要當作是函數來使用,還要當作是對象來使用。具體我們可以在 .d.ts 文件中這么處理。具體代碼如下:
interface JQueryInstance {html: (html: string) => JQueryInstance; } // 函數重載 declare function $(readyFunc: () => void): void; declare function $(selector: string): JQueryInstance;// 如何對對象進行類型定義,以及對類進行類型定義,以及命名空間的嵌套 declare namespace $ { namespace fn {class init {}} }大家可以看到,我們定義了命名空間,并在命名空間里賣弄繼續嵌套命名空間,同時,用 class 類進行了類型定義。最終,我們就可以成功訪問到 $.fn.init() 啦!
4、npm模塊化引入
我們也可以安裝對應的第三方庫的 npm 包,這個包是類型定義文件。假如我們現在要引入一個 JQuery 庫,那么我們可以這么處理。
npm install --save @type/jquery接下來我們對 .d.ts 文件進行改造,具體代碼如下:
// ES6 模塊化 declare module 'jquery' {interface JQueryInstance {html: (html: string) => JQueryInstance;}// 混合類型function $(readyFunc: () => void): void;function $(selector: string): JQueryInstance;namespace $ {namespace fn {class init {}}}export = $; }最后,來到我們想要引入 $ 的 ts 文件中。具體代碼如下:
import $ from 'jquery';$(function() {$('body').html('<div>123</123>');new $.fn.init(); });十一、內置類型
我們在寫 ts 代碼時,其實不知不覺已經使用了很多的內置對象。對象呢,是指根據標準(標準指 ECMA 、 DOM 等標準),在全局作用域 global 上面存在的對象。那我們在運行 tsc 時,這些內置的對象就會被當作附加的禮物給程序加載進行。接下來我們來體會一下幾種常見的內置對象。
全局對象:
// global object 全局對象 const a: Array<number> = [1, 2, 3] const date = new Date() date.getTime() const reg = /abc/ reg.test('abc')內置對象:
// build-in object 內置對象 Math.pow(2, 2)DOM和BOM對象:
// DOM 和 BOM let body = document.body let allLis = document.querySelectorAll('li') allLis.keys()document.addEventListener('click', e => {e.preventDefault() })功能性類型:
// Utility Types 功能性類型 interface IPerson{name: stringage: number }let monday: IPerson = {name: 'monday', age: 20}//可選屬性 type IPartial = Partial<IPerson> let monday2: Ipartial = {name: 'monday'}//移除某一個屬性 type Omit = Omit<IPerson, 'name'> let monday3: Omit = {age: 20}十二、TypeScript中的配置文件
往往我們在剛初始化一個 ts 項目時,都會先運行 tsc --init ,之后呢,會生成一個 tsconfig.json 文件。在這個文件下呢,有很多的配置。那接下來,我們就來分析下其中一些比較值得注意的配置項。
{"include": ["./demo.ts"], // 只編譯 ./demo.ts 文件"exclude": ["./demo.ts"], // 不編譯 ./demo.ts 文件"files": ["./demo.ts"], // 只編譯 ./demo.ts 文件"removeComments": true, // 表示打包時移除掉ts文件中的注釋"noImplicityAny": true, // 當設置為true時,表示所有的參數都應該設置類型,否則會報錯;當設置為false時,則不要求顯式地設置any"strictNullChecks": true, // 當設置為true時,表示強制檢查null類型;當設置為false時,則表示不強制檢查null類型"rootDir": "./src", // 指定輸入的文件的地址"outFile": "./build/page.js", // 將所有文件最終統一打包到build目錄下的page.js文件里"outDir": "./build", // 指定輸出的文件的地址"incremental": true, // 增量編譯,即之前編譯過的現在就不用編譯了"allowJs": true, // 允許指定文件夾下的所有文件進行編譯"checkJs": true, // 對編譯后的js文件進行語法檢測"sourceMap": true, // 編譯后的結果再生成一個 .map 文件"noUnusedLocals": true, // 對寫出的多余的,但是又沒有實際作用的代碼進行提示"noUnuesdParameters": true, // 對函數的參數進行校驗,如果函數中的參數沒有進行使用,則會錯誤提示"baseUrl": "./", // TypeScript下的根路徑是什么路徑 }十三、在ts中對代碼進行模塊化組織
上面我們看著命名空間的使用方法似乎還有一點麻煩。那事實上,在 ts 中,我們還可以對代碼進行模塊化組織,通常是通過 import 語句來處理。怎么處理呢?
1、項目結構
2、模塊化拆分
第一步,先來定義 src|components.ts 下的代碼。具體代碼如下:
export class Header {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Header';document.body.appendChild(elem);} }export class Content {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Content';document.body.appendChild(elem);} }export class Footer {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Footer';document.body.appendChild(elem);} }第二步,使用模塊化的方式進行調用。在 src|page.ts 文件下,具體代碼如下:
import { Header, Content, Footer } from './components';export class Page {constructor() {new Header();new Content();new Footer();} }大家可以看到,上面我們使用了 import 語句,來將類進行模塊化調用。
第三步,在項目的 index.html 中引用,最終運行。具體代碼如下:
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8" /><title>Document</title><script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script><script src="./build/page.js"></script> </head><body><script>require(['page'], function (page) {new page.Page();});</script> </body></html>大家可以看到,如果是在沒有使用 webpack 等打包工具的情況下,那么我們需要使用 cdn 的方式去引入一個 require 的庫,以便于后續可以使用 require 這種語法。
最終我們來看下瀏覽器的顯示效果。如下圖:
可以看到,最終展示除了我們想要的效果。那在上面中,我們就簡單了解了在 TypeScript 中,如何通過 import 語句來對模塊進行拆分和組合。
十四、結束語
關于 ts 的入門講到這里就結束啦!希望大家能對 ts 有一個簡單的認識!
如果這篇文章對你有用,記得留個腳印jio再走哦~
彩蛋 One More Thing
(:專欄直通車
專欄點這里👉https://juejin.cn/column/6979926803238354952
(:番外
注:這篇文章是對上一篇文章的補充,增加了一些進階語法。
以上就是本文的全部內容,我們下期見!🥂🥂🥂
總結
以上是生活随笔為你收集整理的还不会ts?一文带你打开ts的大门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 「软件项目管理」成本估算模型——Wals
- 下一篇: 因零部件运输不当,北京奔驰召回 34 辆