快速转 TypeScript 指南
From:https://segmentfault.com/a/1190000040582994
TypeScript 教程:https://www.runoob.com/typescript/ts-tutorial.html
TypeScript 入門教程:http://ts.xcatliu.com/
TypeScript 超詳細入門教程(上):https://blog.csdn.net/Aria_Miazzy/article/details/105641241
TypeScript 超詳細教程:https://www.jianshu.com/p/c0ca03cffa62
5分鐘上手TypeScript:https://www.tslang.cn/docs/handbook/typescript-in-5-minutes.html
Typescript 手冊指南:https://www.tslang.cn/docs/handbook/basic-types.html
TypeScript 中文手冊:http://www.runoob.com/manual/gitbook/TypeScript/_book/
深入理解 TypeScript:https://jkchao.github.io/typescript-book-chinese/
TypeScript is?JavaScript with syntax for types.?TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.
types 和 @types
types 和 @types:https://zhuanlan.zhihu.com/p/194196536
解決TypeScript報錯TS2304: cannot find name ' require':npm install @types/node --save-dev
1、為什么要用 TypeScript
TypeScript可以讓我們開發中避免一些類型或者一些不是我們預期希望的代碼結果錯誤。xxx is not defined 我們都知道JavaScript錯誤是在運行中才拋出的,但是TypeScript錯誤直接是在編輯器里告知我們的,這極大的提升了開發效率,也不用花大量的時間去寫單測,同時也避免了大量的時間排查Bug。
什么是 TypeScript ?
TypeScript 是一種由微軟開發的自由和開源的編程語言,它是 JavaScript 的一個超集,擴展了 JavaScript 的語法。
語法特性
- 類 Classes
- 接口 Interfaces
- 模塊 Modules?
- 類型注解 Type annotations
- 編譯時類型檢查 Compile time type checking?
- Arrow 函數 (類似 C# 的 Lambda 表達式)
JavaScript 與 TypeScript 的區別
TypeScript 是 JavaScript 的超集,擴展了 JavaScript 的語法,因此現有的 JavaScript 代碼可與 TypeScript 一起工作無需任何修改,TypeScript 通過類型注解提供編譯時的靜態類型檢查。
TypeScript 可處理已有的 JavaScript 代碼,并只對其中的 TypeScript 代碼進行編譯。
2、TypeScript 優缺點
優點
- 一般我們在前后端聯調時,都要去看接口文檔上的字段類型,而TypeScript會自動幫我們識別當前的類型。節省了我們去看文檔或者network時間。這叫做類型推導(待會我們會講到)
- 友好地在編輯器里提示錯誤,避免代碼在運行時類型隱式轉換踩坑。
缺點
- 有一定的學習成本,TypeScript中有幾種類型概念,interface接口、class類、enum枚舉、generics泛型等這些需要我們花時間學習。
- 可能和一些插件庫結合的不是很完美
3、TypeScript 和 JavaScript 的運行流程
JavaScript 運行流程
依賴 NodeJs 環境和瀏覽器環境
- 將JavaScript代碼轉換為JavaScript-AST
- 將AST代碼轉換為字節碼
- 運算時計算字節碼
TypeScript 運行流程
以下操作均為 TSC 操作。前三步執行完后,后面的繼續同上操作。
- 將TypeScript代碼編譯為?TypeScript-AST
- 檢查AST代碼上類型檢查
- 類型檢查后,編譯為JavaScript代碼
- JavaScript代碼轉換為JavaScript-AST
- 將AST代碼轉換為字節碼
- 運算時計算字節碼
4、TypeScript 和 JavaScript 區別
只有搞懂了二者的區別,我們才可以更好的理解TypeScript
| 類型是如何綁定? | 動態 | 靜態 |
| 是否存在類型隱式轉換? | 是 | 否 |
| 何時檢查類型? | 運行時 | 編譯時 |
| 何時報告錯誤 | 運行時 | 編譯時 |
類型綁定
- JavaScript:JavaScript動態綁定類型,只有運行程序才能知道類型,在程序運行之前JavaScript對類型一無所知
- TypeScript:TypeScript是在程序運行前(也就是編譯時)就會知道當前是什么類型。當然如果該變量沒有定義類型,那么TypeScript會自動類型推導出來。
類型轉換
- JavaScript:比如在JavaScript中1 + true這樣一個代碼片段,JavaScript存在隱式轉換,這時true會變成number類型number(true)和1相加。
- TypeScript:在TypeScript中,1+true這樣的代碼會在TypeScript中報錯,提示number類型不能和boolean類型進行運算。
何時檢查類型
- JavaScript:在JavaScript中只有在程序運行時才能檢查類型。類型也會存在隱式轉換,很坑。
- TypeScript:在TypeScript中,在編譯時就會檢查類型,如果和預期的類型不符合直接會在編輯器里報錯、爆紅
何時報告錯誤
- JavaScript:在JavaScript只有在程序執行時才能拋出異常,JavaScript存在隱式轉換,等我們程序執行時才能真正的知道代碼類型是否是預期的類型,代碼是不是有效。
- TypeScript:在TypeScript中,當你在編輯器寫代碼時,如有錯誤則會直接拋出異常,極大得提高了效率,也是方便。
5、TypeScript 的兩種模式
顯式注解類型 (類型批注)
let name: string = "前端娛樂圈"; let age: number = 38; let hobby: string[] = ["write code", "玩游戲"]顯式注解類型就是,聲明變量時定義上類型(官方話語就是聲明時帶上注解),讓我們一看就明白,哦~,這個name是一個string類型。
TypeScript 通過類型批注提供靜態類型以在編譯時啟動類型檢查。這是可選的,而且可以被忽略而使用 JavaScript 常規的動態類型。
function Add(left: number, right: number): number {return left + right; }對于基本類型的批注是 number, bool和string。而弱或動態類型的結構則是any類型。
類型批注可以被導出到一個單獨的聲明文件以讓使用類型的已被編譯為JavaScript的TypeScript腳本的類型信息可用。批注可以為一個現有的JavaScript庫聲明,就像已經為Node.js和jQuery所做的那樣。
當類型沒有給出時,TypeScript編譯器利用類型推斷以推斷類型。如果由于缺乏聲明,沒有類型可以被推斷出,那么它就會默認為是動態的any類型。
示例:在?type.ts 文件中創建一個簡單的 area() 函數:
function area(shape: string, width: number, height: number) {var area = width * height;return "I'm a " + shape + " with an area of " + area + " cm squared."; }document.body.innerHTML = area("rectangle", 30, 15);創建一個 index.html 文件:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Learning TypeScript</title> </head> <body> <script src="hello.js"></script> </body> </html>修改?index.html?的 js 文件為?type.js?然后編譯 TypeScript 文件:?tsc type.ts。
瀏覽器刷新 index.html 文件,輸出結果如下:
推導類型
let name = "前端娛樂圈"; // 是一個string類型 let age = 38; // 是一個number類型 let hobby = ["write code", "玩游戲"] // 是一個string數組類型推導類型就是去掉顯示注解,系統自動會識別當前值是一個什么類型的。
6、安裝 TypeScript && 運行
安裝 typescript
我們可以通過以下兩種方式來安裝 TypeScript:
- 通過 Node.js 包管理器 (npm)
- 通過與 Visual Studio 2012 繼承的 MSI. (Click here to download)?
通過 npm 按安裝的步驟:
1、安裝 npm ( 安裝完 nodejs 后,npm 可以直接使用?)
$ curl http://npmjs.org/install.sh | sh $ npm --version2、安裝 TypeScript npm 包:
$ npm install -g typescript安裝完成后我們就可以使用 TypeScript 編譯器,名稱叫 tsc,可將編譯結果生成 js 文件。
要編譯 TypeScript 文件,可使用如下命令:
tsc filename.ts一旦編譯成功,就會在相同目錄下生成一個同名 js 文件,你也可以通過命令參數來修改默認的輸出名稱。默認情況下編譯器以 ECMAScript 3(ES3)為目標但 ES5 也是受支持的一個選項。TypeScript 增加了對為即將到來的 ECMAScript 6 標準所建議的特性的支持。
我們知道了運行?tsc?命令就可以編譯生成一個文件,有的小伙伴覺得這樣太麻煩了,每次運行只是編譯出來一個文件還不是運行,還得用node index.js才可以運行。不急我們接著往下看
ts-node 插件
我們來看一下這個插件?ts-node,這個插件可以直接運行.ts文件,并且也不會編譯出來.js文件。
npm i ts-node // 運行 ts-node index.ts通過 MSI 文件安裝時的界面:
TypeScript 的?Hello World
創建 hello.ts 文件, *.ts 是 TypeScript 文件的后綴,向 hello.ts 文件添加如下代碼:
alert('hello world in TypeScript!');接下來,我們打開命令行,使用 tsc 命令編譯 hello.ts 文件:
$ tsc hello.ts在相同目錄下就會生成一個 hello.js 文件,然后打開 index.html 輸出結果如下:
7、TypeScript 基礎知識
箭頭函數表達式( lambda表達式 )
lambda 表達式?()=>{something} 或 ()=>something?相當于 js 中的函數,它的好處是可以自動將函數中的 this 附加到上下文中。示例:
var shape = {name: "rectangle",popup: function() {console.log('This inside popup(): ' + this.name);setTimeout(function() {console.log('This inside setTimeout(): ' + this.name);console.log("I'm a " + this.name + "!");}, 3000);} };shape.popup();實例中的 this.name 是一個空值:
接下來我們使用 TypeScript 的箭頭函數。把?function()?替換為?() =>:
var shape = {name: "rectangle",popup: function() {console.log('This inside popup(): ' + this.name);setTimeout( () => {console.log('This inside setTimeout(): ' + this.name);console.log("I'm a " + this.name + "!");}, 3000);} };shape.popup();輸出結果如下:
在以上實例編譯后端 js 文件中,我們可以看到一行?var _this = this;,_this?在 setTimeout() 的回調函數引用了 name 屬性。
7.1 基礎靜態類型
在 TypeScript 中基礎類型跟我們 JavScript 中基礎類型是一樣的。只是有各別是 Ts 里面新出的。
1. number
const count: number = 18;? // 顯示注解一個 number 類型 const count1 = 18;? // 不顯示注解,ts 會自動推導出來類型2. string
const str: string = "前端娛樂圈"; // 顯示注解一個string類型 const str1 = "蛙人"; // 不顯示注解,ts會自動推導出來類型3. boolean
const status: string = false; // 顯示注解一個string類型 const status1 = true; // 不顯示注解,ts會自動推導出來類型4. null
const value: null = null;// 這一點null類型可以賦值undefined跟在 js中是一樣的,null == undefined const value: null = undefined;5. undefined
const value: undefined = undefined;// 這一點null類型可以賦值undefined跟在 js中是一樣的,null == undefined const value: undefined = null;6. void
字面意思是 "無效" ,早些項目里面會有 <a href="javascript: void(0)"> 這是控制 a 標簽的跳轉默認行為。你不管怎么執行 void 方法它都是返回 undefined。在 TypeScript 中 void 類型是什么呢。它也是代表無效的,一般只用在函數上,告訴別人這個函數沒有返回值。
function fn(): void {} // 正確function testFn(): void {return 1; // 報錯,不接受返回值存在 }function fn1(): void { return undefined} // 顯示返回undefined類型,也是可以的function fn2(): void { return null} // 顯示返回null類型也可以,因為 null == undefined7. never
never "一個永遠不會有值的類型"?或者 也可以說 "一個永遠也執行不完的類型",代表用于不會有值,undefined、null也算做是值。一般這個類型就不會用到,也不用。大家知道這個類型就行。
const test: never = null; // 錯誤 const test1: never = undefined // 錯誤function Person(): never { // 正確,因為死循環了,一直執行不完while(true) {} }function Person(): never { // 正確,因為遞歸,永遠沒有出口Person() }function Person(): never { // 正確 代碼報錯了,執行不下去throw new Error() }8. any
any?這個類型代表?任何的、任意的。希望大家在項目中,不要大片定義any類型。雖然它真的好使,那這樣我們寫TypeScript就沒有任何意義了。
let value: any = ""; // 正確 value = null // 正確 value = {} // 正確 value = undefined // 正確9. unknown
unknown?類型是我們?TypeScript?中第二個any類型,也是接受任意的類型的值。它的英文翻譯過來就是未知的,我們來看一下栗子
let value: unknown = "" value = 1; value = "fdsfs" value = null value = {}unknown 和 any 區別
那現在肯定有小伙伴疑惑,誒,那它?unknown?相當于是any類型,那二者的區別是什么。我們來看一下
let valueAny: any = ""; let valueUnknown: unknown = "";valueAny = "蛙人"; valueUnknown = "前端娛樂圈"let status: null = false; status = valueAny; // 正確 status = valueUnknown // 報錯,不能將unknown類型分配給null類型我們來看一下上面的,為什么any類型就能被賦值成功,而unknown類型不行呢,從它倆的意義來上看,還是有點區別的,any任何的,任意的、unknown未知的。所以你給unknown類型賦值任何類型都沒關系,因為它本來就是未知類型嘛。但是你如果把它的unknown類型去被賦值一個null類型,這時人家null這邊不干了,我不接受unknown類型。
說白了一句話:
- 別人不接受 unknown 類型
- 而 unknown 類型接受別人
7.2?對象靜態類型
說起對象類型,我們肯定都能想到對象包含{}、數組、類、函數
1. object && {}
其實這倆意思一樣,{}、object表示非原始類型,也就是除number,string,boolean,symbol,null或undefined之外的類型。
const list: object = {} // 空對象 const list1: object = null; // null對象 const list: object = [] // 數組對象 const list: {} = {} list.name = 1 // 報錯 不可更改里面的字段,但是可以讀取 list.toString()2. 數組
const list: [] = []; // 定義一個數組類型 const list1: number[] = [1,2] // 定義一個數組,里面值必須是number const list2: object[] = [null, {}, []] // 定義一個數組里面必須是對象類型的 const list3: Array<number> = [1,2,3] // 泛型定義數組必須是number類型,泛型我們待會講到3. 類
// 類 class ClassPerson = {name: "前端娛樂圈" }const person: ClassPerson = new Person(); person.xxx = 123; // 這行代碼報錯,因為當前類中不存在該xxx屬性4. 函數
// 函數。定義一個變量必須是函數類型的,返回值必須是string類型 const fn: () => string = () => "前端娛樂圈"7.3?函數類型注解
這里說一下函數顯示注解和函數參數不會類型推導問題。
1. 函數返回類型為 number
function fn(a, b): number {return a + b; } fn(1, 2)2. 函數 void
顯示注解為void類型,函數沒有返回值。
function fn(): void {console.log(1) }3. 函數不會自動類型推導
可以看到下面的函數類型,不會自動類型推導,我們實參雖然傳入的1和2,但是形參方面是可以接受任意類型值的,所以系統也識別不出來你傳遞的什么,所以這里得需要我們顯示定義注解類型。
function testFnQ(a, b) {return a + b } testFnQ(1,2)我們來改造一下。?
function testFnQ(a:number, b:number) {return a + b } testFnQ(1,2)我們再來看一下參數對象顯示注解類型,也是在:號后面賦值每個字段類型即可。?
function testFnQ(obj : {num: number}) {return obj.num } testFnQ({num: 18})7.4?元組 Tuple
元組用于表示一個已知數組的數量和類型的數組,定義數組中每一個值的類型,一般不經常使用。
const arr: [string, number] = ["前端娛樂圈", 1] const arr: [string, string] = ["前端娛樂圈", 1] // 報錯7.5?枚舉 Enum
Enum?枚舉類型,可以設置默認值,如果不設置則為索引。
enum color {RED,BLUE = "blue",GREEN = "green" }// color["RED"] 0 // color["BLUE"] blue像上面的 color 中 RED 沒有設置值,那么它的值則為 0,如果 BLUE 也不設置的話那么它的值則是1,它們這里是遞增。如果設置值則是返回設置的值
注意這里還有一個問題,直接來上代碼
通過上面學習我們知道了enum可以遞增值,也可以設置默認值。但是有一點得注意一下,enum沒有json對象那樣靈活,enum不能在任意字段上設置默認值。
比如下面栗子,RED沒有設置值,然后BLUE設置了默認值,但是GREEN又沒有設置,這時這個GREEN會報錯。因為你第二個BLUE設置完默認值,第三又不設置,這時代碼都不知道該咋遞增了,所以報錯。還有一種方案就是你給BLUE可以設置一個數字值,這時第三個GREEN不設置也會跟著遞增,因為都是number類型。
// 報錯 enum color {RED,BLUE = "blue",GREEN }// good enum color {RED, // 0BLUE = 4, // 4GREEN // 5 }比如enum枚舉類型還可以反差,通過value查key值。像我們json對象就是不支持這種寫法的。
enum color {RED, // 0BLUE = 4, // 4GREEN // 5 }console.log(color[4]) // BLUE console.log(color[0]) // RED7.6 接口 Interface
接口interface是什么,接口interface就是方便我們定義一處代碼,多處復用。接口里面也存在一些修飾符。下面我們來認識一下它們吧。
1. 接口怎么復用
比如在講到這之前,我們不知道接口這東西,可能需要給對象定義一個類型的話,你可能會這樣做。
const testObj: { name: string, age: number } = { name: "前端娛樂圈", age: 18 } const testObj1: { name: string, age: number } = { name: "蛙人", age: 18 }我們用接口來改造一下。
interface Types {name: string, age: number }const testObj: Types = { name: "前端娛樂圈", age: 18 } const testObj1: Types = { name: "蛙人", age: 18 }可以看到使用interface關鍵字定義一個接口,然后賦值給這兩個變量,實現復用。
2. readonly 修飾符
readonly類型,只可讀狀態,不可更改。
interface Types {readonly name: string, readonly age: number }const testObj: Types = { name: "前端娛樂圈", age: 18 } const testObj1: Types = { name: "蛙人", age: 18 } testObj.name = "張三" // 無法更改name屬性,因為它是只讀屬性 testObj1.name = "李四" // 無法更改name屬性,因為它是只讀屬性3. ?可選修飾符
可選修飾符以?定義,為什么需要可選修飾符呢,因為如果我們不寫可選修飾符,那interface里面的屬性都是必填的。
interface Types {readonly name: string, readonly age: number,sex?: string }const testObj: Types = { name: "前端娛樂圈", age: 18}4. extends繼承
我們的interface也是可以繼承的,跟ES6Class類一樣,使用extends關鍵字。
interface Types {readonly name: string, readonly age: number,sex?: string }interface ChildrenType extends Types { // 這ChildrenType接口就已經繼承了父級Types接口hobby: [] }const testObj: ChildrenType = { name: "前端娛樂圈", age: 18, hobby: ["code", "羽毛球"] }5. propName擴展
interface里面這個功能就很強大,它可以寫入不在interface里面的屬性。
interface Types {readonly name: string, readonly age: number,sex?: string, }const testObj: Types = { name: "前端娛樂圈", age: 19, hobby: [] }上面這個testObj這行代碼會爆紅,因為hobby屬性不存在interface接口中,那么我們不存在的接口中的,還不讓人家寫了?。這時候可以使用自定義就是上面的propName。
interface Types {readonly name: string, readonly age: number,sex?: string,[propName: string]: any // propName字段必須是 string類型 or number類型。 值是any類型,也就是任意的 }const testObj: Types = { name: "前端娛樂圈", age: 19, hobby: [] }在運行上面代碼,就可以看到不爆紅了~
示例:創建一個 interface.ts 文件,修改?index.html?的 js 文件為?interface.js。
interface.js 文件代碼如下:interface Shape {name: string;width: number;height: number;color?: string; }function area(shape : Shape) {var area = shape.width * shape.height;return "I'm " + shape.name + " with area " + area + " cm squared"; }console.log( area( {name: "rectangle", width: 30, height: 15} ) ); console.log( area( {name: "square", width: 30, height: 30, color: "blue"} ) );接口可以作為一個類型批注。編譯以上代碼?tsc interface.ts?不會出現錯誤,但是如果你在以上代碼后面添加缺失 name 參數的語句則在編譯時會報錯:
console.log( area( {width: 30, height: 15} ) );重新編譯,錯誤信息如下:
$ tsc hello.ts?
hello.ts(15,20): error TS2345: Argument of type '{ width: number; height: number; }' is not assignable to parameter of type 'Shape'.
? Property 'name' is missing in type '{ width: number; height: number; }'.
瀏覽器訪問,輸出結果如下:
7.7?Type
我們再來看一下Type,這個是聲明類型別名使的,別名類型只能定義是:基礎靜態類型、對象靜態類型、元組、聯合類型。
注意:type 別名不可以定義 interface type Types = string;type TypeUnite = string | numberconst name: typeUnite = "前端娛樂圈" const age: typeUnite = 18那么 type 類型別名和 interface 接口有什么區別呢
1. type不支持 interface 聲明
type Types = number type Types = string // 報錯, 類型別名type不允許出現重復名字interface Types1 {name: string }interface Types1 {age: number }// interface接口可以出現重復類型名稱,如果重復出現則是, // 合并起來也就是變成 { name:string, age: number }第一個Types類型別名type不允許出現重復名字,interface接口可以出現重復類型名稱,如果重復出現則是,合并起來也就是變?{ name:string, age: number }
再來看一下interface另一種情況
interface Types1 {name: string }interface Types1 {name: number }可以看到上面兩個同名稱的interface接口,里面的屬性也是同名稱,但是類型不一樣。這第二個的Types1就會爆紅,提示:后續聲明的接口,必須跟前面聲明的同名屬性類型必須保持一致,把后續聲明的name它類型換成string即可。
2. type支持表達式 interface不支持
const?count:?number?=?123 type?testType?=?typeof?countconst count: number = 123interface testType {[name: typeof count]: any // 報錯 }可以看到上面type支持表達式,而interface不支持
3. type 支持類型映射,interface不支持
type keys = "name" | "age" type KeysObj = {[propName in keys]: string }const PersonObj: KeysObj = { // 正常運行name: "蛙人",age: "18" } interface testType {[propName in keys]: string // 報錯 }7.8 聯合類型
聯合類型用|表示,說白了就是滿足其中的一個類型就可以。
const statusTest: string | number = "前端娛樂圈"const flag: boolean | number = true再來看一下栗子。我們用函數參數使用聯合類型看看會發生什么
function testStatusFn(params: number | string) {console.log(params.toFixed()) // 報錯 }testStatusFn(1)上面我們說過了,函數參數類型不能類型自動推導,更何況現在用上聯合類型,系統更懵逼了,不能識別當前實參的類型。所以訪問當前類型上的方法報錯。
接下來帶大家看一些?類型保護,聽著挺高級,其實這些大家都見過。
1. typeof
function testStatusFn(params: number | string) {console.log(params.toFixed()) // 報錯 } testStatusFn(1)改造后
// 正常 function testStatusFn(params: string | number) {if (typeof params == "string") {console.log(params.split)}if (typeof params == "number") {console.log(params.toFixed)} }testStatusFn(1)2. in
// 報錯 interface frontEnd {name: string }interface backEnd {age: string }function testStatusFn(params: frontEnd | backEnd) {console.log(params.name) }testStatusFn({name: "蛙人"})改造后
// 正常 function testStatusFn(params: frontEnd | backEnd) {if ("name" in params) {console.log(params.name)}if ("age" in params) {console.log(params.age)} }testStatusFn({name: "蛙人"})3. as 斷言
// 報錯 interface frontEnd {name: string }interface backEnd {age: string }function testStatusFn(params: frontEnd | backEnd) {console.log(params.name) }testStatusFn({name: "蛙人"})改造后
// 正常 function testStatusFn(params: frontEnd | backEnd) {if ("name" in params) {const res = (params as frontEnd).nameconsole.log(res)}if ("age" in params) {const res = (params as backEnd).ageconsole.log(res)} }testStatusFn({age: 118})7.9?交叉類型
交叉類型就是跟聯合類型相反,它用&表示,交叉類型就是兩個類型必須存在。這里還用上面的聯合類型的栗子來看下。
interface frontEnd {name: string }interface backEnd {age: number }function testStatusFn(params: frontEnd & backEnd) {}testStatusFn({age: 118, name: "前端娛樂圈"})這里我們可以看到實參必須傳入兩個接口(interface)全部的屬性值才可以。聯合類型是傳入其中類型就可以。
注意:我們的接口interface出現同名屬性
interface frontEnd {name: string }interface backEnd {name: number }function testStatusFn(params: frontEnd & backEnd) {console.log(params) }testStatusFn({name: "前端"})上面我們兩個接口類型中都出現了同名屬性,但是類型不一樣,這時類型就會變為never。
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/51e337707b6641e590bbb0139fabacdb~tplv-k3u1fbpfcp-watermark.image" width="90%">
7.10?泛型
泛型是TypeScript中最難理解的了,這里我盡量用通俗易懂的方式講明白。
function test(a: string | number, b: string | number) {console.log(a, b) } test(1, "前端娛樂圈")比如上面栗子,函數參數注解類型定義string和number,調用函數實參傳入也沒什么問題,但是有個需求,就是實參我們必須傳入同樣的類型(傳入兩個number類型)。雖然上面這種聯合類型也可以實現,但是如果我們要在加一個boolean類型,那么聯合類型還得在追加一個boolean,那這樣代碼太冗余了。
這時就需要用到泛型了,泛型是專門針對不確定的類型使用,并且靈活。泛型的使用大部分都是使用<T>,當然也可以隨便使用,如:<Test>、<Custom>都可以。
function test<T>(a: T, b: T) {console.log(a, b) }// 調用后面跟著尖括號這就是泛型的類型,這時報錯, // 因為在調用的使用類型是number,所以只能傳入相同類型的 test<number>(1, "前端娛樂圈") test<boolean>(true, false) test<string>("前端娛樂圈", "蛙人")上面這使用泛型就解決了我們剛才說的傳入同一個類型參數問題,但是泛型也可以使用不同的參數,可以把調用類型定義為<any>
function test<T>(a: T, b: T) {console.log(a, b) }test<any>(1, "前端娛樂圈")但是上面這種又有一種問題,它可以傳入對象,但是如果我們只希望傳入number類型和string類型。那么我們泛型也給我們提供了約束類型。泛型使用extends進行了類型約束,只能選擇string、number類型。
function test<T extends number | string, Y extends number | string>(a: T, b: Y) {console.log(a, b) }test<number, string>(18, "前端娛樂圈") test<string, number>("前端娛樂圈", 18)這時,傳入泛型時使用,逗號分隔,來定義每一個類型希望是什么。記住,只有我們不確定的類型,可以使用泛型。
7.11?模塊
TypeScript也支持import和export這里大多數小伙伴都知道,這里都不多講啦。
// 導入 import xxx, { xxx } from "./xxx"// 導出 export default {} export const name = "前端娛樂圈"如有不明白的小伙伴,可以看我以前文章 聊聊什么是CommonJs和Es Module及它們的區別:?https://juejin.cn/post/6938581764432461854
7.12?Class類
以下這三個修飾符是在TypeScript類中才能使用,在JavaScript類中是不支持的。1. public
public為類的公共屬性,就是不管在類的內部還是外部,都可以訪問該類中屬性及方法。默認定義的屬性及方法都是public。
class Person {name = "前端娛樂圈";public age = 18; } const res = new Person(); console.log(res.name, res.age) // 前端娛樂圈 18上面可以看到打印結果都能顯示出來,name屬性沒有定義public公共屬性,所以類里面定義的屬性及方法默認都是public定義。
2. private
private為類的私有屬性,只有在當前類里面才能訪問,當前類就是{}里面區域內。在{}外面是不能訪問private定義的屬性及方法的
class Person {private name = "前端娛樂圈";private age = 18; } const res = new Person(); console.log(res.name, res.age) // 這倆行會爆紅,當前屬性為私有屬性,只能在類內部訪問class Scholl extends Person {getData() {return this.username + "," + this.age} } const temp = new Scholl()// 爆紅~,雖然繼承了Person類,但是private定義是只能在當前類訪問,子類也不能訪問。 console.log(temp.getData())3. protected
protected為類的保護屬性,只有在當前類和子類可以訪問。也就是說用protected屬性定義的子類也可以訪問。
class Person {protected username = "前端娛樂圈";protected age = 18; } const res = new Person(); console.log(res.name, res.age) // 這倆行會爆紅,當前屬性為私有屬性,只能在類內部訪問class Scholl extends Person {getData() {return this.username + "," + this.age} } const temp = new Scholl() console.log(temp.getData()) // 前端娛樂圈,18??梢哉TL問父類的屬性4. implements
implements關鍵字只能在class中使用,顧名思義,實現一個新的類,從父級或者從接口實現所有的屬性和方法,如果在PersonAll類里面不寫進去接口里面已有的屬性和方法則會報錯。
interface frontEnd {name: string,fn: () => void }class PersonAll implements frontEnd {name: "前端娛樂圈";fn() {} }5. 抽象類
抽象類使用abstract關鍵字定義。abstract抽象方法不能實例化,如果,抽象類里面方法是抽象的,那么本身的類也必須是抽象的,抽象方法不能寫函數體。父類里面有抽象方法,那么子類也必須要重新該方法。
// 抽象類 abstract class Boss {name = "秦";call() {} // 抽象方法不能寫函數體 }class A extends Boss {call() {console.log(this.name);console.log("A")} }class B extends Boss {call() {console.log("B")} }new A().call()該抽象類使用場景,比如A需求或者B需求正好需要一個公共屬性,然后本身還有一些自己的邏輯,就可以使用抽象類,抽象類只能在TypeScript中使用。
示例:接下來我們創建一個類文件 class.ts,代碼如下:
class Shape {area: number;color: string;constructor ( name: string, width: number, height: number ) {this.area = width * height;this.color = "pink";};shoutout() {return "I'm " + this.color + " " + this.name + " with an area of " + this.area + " cm squared.";} }var square = new Shape("square", 30, 30);console.log( square.shoutout() ); console.log( 'Area of Shape: ' + square.area ); console.log( 'Name of Shape: ' + square.name ); console.log( 'Color of Shape: ' + square.color ); console.log( 'Width of Shape: ' + square.width ); console.log( 'Height of Shape: ' + square.height );以上 Shape 類中有兩個屬性 area 和 color,一個構造器 (constructor()), 一個方法是 shoutout() 。
構造器中參數(name, width 和 height) 的作用域是局部變量,所以編譯以上文件,在瀏覽器輸出錯誤結果如下所示:
class.ts(12,42): The property 'name' does not exist on value of type 'Shape'
class.ts(20,40): The property 'name' does not exist on value of type 'Shape'
class.ts(22,41): The property 'width' does not exist on value of type 'Shape'
class.ts(23,42): The property 'height' does not exist on value of type 'Shape'
接下來,我們添加 public 和 private 訪問修飾符。Public 成員可以在任何地方訪問, private 成員只允許在類中訪問。然后修改以上代碼,將 color 聲明為 private,構造函數的參數 name 聲明為 public:
... private color: string; ... constructor ( public name: string, width: number, height: number ) { ...由于 color 成員變量設置了 private,所以會出現以下信息:class.ts(24,41): The property 'color' does not exist on value of type 'Shape'
繼承:繼承使用關鍵字?extends。
接下來我們在 class.ts 文件末尾添加以下代碼,如下所示:
class Shape3D extends Shape {volume: number;constructor ( public name: string, width: number, height: number, length: number ) {super( name, width, height );this.volume = length * this.area;};shoutout() {return "I'm " + this.name + " with a volume of " + this.volume + " cm cube.";}superShout() {return super.shoutout();} }var cube = new Shape3D("cube", 30, 30, 30); console.log( cube.shoutout() ); console.log( cube.superShout() );派生類 Shape3D 說明:
- Shape3D 繼承了 Shape 類, 也繼承了 Shape 類的 color 屬性。
- 構造函數中,super 方法調用了基類 Shape 的構造函數 Shape,傳遞了參數 name, width, 和 height 值。 繼承允許我們復用 Shape 類的代碼,所以我們可以通過繼承 area 屬性來計算 this.volume。
- Shape3D 的 shoutout() 方法重寫基類的實現。superShout() 方法通過使用 super 關鍵字直接返回了基類的 shoutout() 方法。
- 其他的代碼我們可以通過自己的需求來完成自己想要的功能。
7.13 命名空間 namespace
我們學到現在可以看到,不知道小伙伴們發現沒有,項目中文件是不是不能有重復的變量(不管你是不是一樣的文件還是其它文件),否則就直接爆紅了。命名空間一個最明確的目的就是解決重名問題。
命名空間使用namespace關鍵字來定義,示例:index.ts
namespace SomeNameSpaceName { const q = {}export interface obj {name: string} }上面這樣,就定義好了一個命名空間,可以看到變量 q 沒有寫 export 關鍵字,這證明它是內部的變量,就算別的 .ts 文件引用它,它也不會暴露出去。而 interface 這個 obj 接口是可以被全局訪問的。
我們在別的頁面訪問當前命名空間
1. reference引入
/// <reference path="./index.ts" /> namespace SomeNameSpaceName { export class person implements obj {name: "前端娛樂圈"} }2. import
export interface valueData {name: string } import { valueData } from "./xxx.ts"這時使用命名空間之后完全可以解決不同文件重名爆紅問題。
7.14?tsConfig.json
這個tsconfig文件,是我們編譯ts文件,如何將ts文件編譯成我們的js文件。tsc -init這個命令會生成該文件出來哈。執行完該命令,我們可以看到根目錄下會生成一個tsconfig.json文件,里面有一堆屬性。
那么我們怎么將ts文件編譯成js文件呢,直接執行tsc命令可以將根目錄下所有的.ts文件全部編譯成.js文件輸出到項目下。
更多配置文檔,請參考?https://www.tslang.cn/docs/handbook/compiler-options.html
{// include: ["*.ts"] // 執行目錄下所有的ts文件轉換成js文件// include: ["index.ts"] // 只將項目下index.ts文件轉換為js文件// files: ["index.ts"] // 跟include一樣,只執行當前數組值里面的文件,當前files必須寫相對路徑// exclude: ["index.ts"] // exclude就是除了index.ts不執行,其它都執行compilerOptions: {removeComments: true, // 去掉編譯完js文件的注釋outDir: "./build", // 最終輸出的js文件目錄rootDir: "./src", // ts入口文件查找} }8、實用類型
最后來說一下實用類型,TypeScript?標準庫自帶了一些實用類型。這些實用類都是方便接口Interface使用。這里只列舉幾個常用的,更多實用類型?https://www.typescriptlang.org/docs/handbook/utility-types.html
1. Exclude
從一個類型中排除另一個類型,只能是聯合類型,從TypesTest類型中排除UtilityLast類型。
適用于:并集類型
interface UtilityFirst {name: string }interface UtilityLast {age: number }type TypesTest = UtilityFirst | UtilityLast;const ObjJson: Exclude<TypesTest, UtilityLast> = {name: "前端娛樂圈" }2. Extract
Extract正好跟上面那個相反,這是選擇某一個可賦值的聯合類型,從TypesTest類型中只選擇UtilityLast類型。
適用于:并集類型
interface UtilityFirst {name: string }interface UtilityLast {age: number }type TypesTest = UtilityFirst | UtilityLast;const ObjJson: Extract<TypesTest, UtilityLast> = {age: 1 }3. Readonly
把數組或對象的所有屬性值轉換為只讀的。這里只演示一下對象栗子,數組同樣的寫法。
適用于:對象、數組
interface UtilityFirst {name: string }const ObjJson: Readonly<UtilityFirst> = {name: "前端娛樂圈" } ObjJson.name = "蛙人" // 報錯 只讀狀態4. Partial
把對象的所有屬性設置為可選的。我們知道interface只要不設置?修飾符,那么對象都是必選的。這個實用類可以將屬性全部轉換為可選的。
適用于:對象
interface UtilityFirst {name: string }const ObjJson: Partial<UtilityFirst> = {}5. Pick
Pick選擇對象類型中的部分key值,提取出來。第一個參數目標值,第二個參數聯合key
適用于:對象
interface UtilityFirst {name: string,age: number,hobby: [] }const ObjJson: Pick<UtilityFirst, "name" | "age"> = {name: "前端娛樂圈",age: 18 }6. Omit
Omit選擇對象類型中的部分key值,過濾掉。第一個參數目標值,第二個參數聯合key
適用于:對象
interface UtilityFirst {name: string,age: number,hobby: string[] }const ObjJson: Omit<UtilityFirst, "name" | "age"> = {hobby: ["code", "羽毛球"] }7. Required
Required把對象所有可選屬性轉換成必選屬性。
適用于:對象
interface UtilityFirst {name?: string,age?: number,hobby?: string[] }const ObjJson: Required<UtilityFirst> = {name: "蛙人",age: 18,hobby: ["code"] }8. Record
創建一個對象結果集,第一個參數則是key值,第二個參數則是value值。規定我們只能創建這里面字段值。
適用于:對象
type IndexList = 0 | 1 | 2const ObjJson: Record<IndexList, "前端娛樂圈"> = {0: "前端娛樂圈",1: "前端娛樂圈",2: "前端娛樂圈" }參考資料
- https://www.cnblogs.com/mengf...
- 了不起的 TypeScript 入門教程
- TypeScript編程實體書
- TypeScript中文網
總結
以上是生活随笔為你收集整理的快速转 TypeScript 指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: scrapy 模拟登陆
- 下一篇: 组队竞赛