TypeScript上手指南
前言
?哈嘍,我是樹(shù)醬,今天分享一篇蛙人的typescript指南,自從vue3、還有社區(qū)一些開(kāi)源工具、組件庫(kù)等都開(kāi)始基于typescript開(kāi)發(fā),其自帶的"可選的靜態(tài)類型系統(tǒng)",可以使得當(dāng)我們?cè)趹?yīng)用程序運(yùn)行之前,通過(guò)編譯器就可以顯示有關(guān)任何潛在問(wèn)題的警告
?
一、為什么要用TypeScript
TypeScript可以讓我們開(kāi)發(fā)中避免一些類型或者一些不是我們預(yù)期希望的代碼結(jié)果錯(cuò)誤。xxx is not defined 我們都知道JavaScript錯(cuò)誤是在運(yùn)行中才拋出的,但是TypeScript錯(cuò)誤直接是在編輯器里告知我們的,這極大的提升了開(kāi)發(fā)效率,也不用花大量的時(shí)間去寫單測(cè),同時(shí)也避免了大量的時(shí)間排查Bug。
二、TypeScript優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
一般我們?cè)谇昂蠖寺?lián)調(diào)時(shí),都要去看接口文檔上的字段類型,而
TypeScript會(huì)自動(dòng)幫我們識(shí)別當(dāng)前的類型。節(jié)省了我們?nèi)タ?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">文檔或者network時(shí)間。這叫做類型推導(dǎo)(待會(huì)我們會(huì)講到)友好地在編輯器里提示錯(cuò)誤,避免代碼在運(yùn)行時(shí)類型隱式轉(zhuǎn)換踩坑。
缺點(diǎn)
有一定的學(xué)習(xí)成本,
TypeScript中有幾種類型概念,interface接口、class類、enum枚舉、generics泛型等這些需要我們花時(shí)間學(xué)習(xí)。可能和一些插件庫(kù)結(jié)合的不是很完美
三、TypeScript運(yùn)行流程及JavaScript代碼運(yùn)行流程
「1. JavaScript運(yùn)行流程如下,依賴NodeJs環(huán)境和瀏覽器環(huán)境」
將 JavaScript代碼轉(zhuǎn)換為JavaScript-AST將 AST代碼轉(zhuǎn)換為字節(jié)碼運(yùn)算時(shí)計(jì)算字節(jié)碼
「2. TypeScript運(yùn)行流程,以下操作均為TSC操作,三步執(zhí)行完繼續(xù)同上操作,讓瀏覽器解析」
將 TypeScript代碼編譯為TypeScript-AST檢查 AST代碼上類型檢查類型檢查后,編譯為 JavaScript代碼JavaScript代碼轉(zhuǎn)換為JavaScript-AST將 AST代碼轉(zhuǎn)換為字節(jié)碼運(yùn)算時(shí)計(jì)算字節(jié)碼
四、TypeScript和JavaScript區(qū)別
只有搞懂了二者的區(qū)別,我們才可以更好的理解TypeScript
| 類型系統(tǒng)特性 | JavaScript | TypeScript |
|---|---|---|
| 類型是如何綁定? | 動(dòng)態(tài) | 靜態(tài) |
| 是否存在類型隱式轉(zhuǎn)換? | 是 | 否 |
| 何時(shí)檢查類型? | 運(yùn)行時(shí) | 編譯時(shí) |
| 何時(shí)報(bào)告錯(cuò)誤 | 運(yùn)行時(shí) | 編譯時(shí) |
類型綁定
「JavaScript」
JavaScript動(dòng)態(tài)綁定類型,只有運(yùn)行程序才能知道類型,在程序運(yùn)行之前JavaScript對(duì)類型一無(wú)所知
「TypeScript」
TypeScript是在程序運(yùn)行前(也就是編譯時(shí))就會(huì)知道當(dāng)前是什么類型。當(dāng)然如果該變量沒(méi)有定義類型,那么TypeScript會(huì)自動(dòng)類型推導(dǎo)出來(lái)。
類型轉(zhuǎn)換
「JavaScript」
比如在JavaScript中1 + true這樣一個(gè)代碼片段,JavaScript存在隱式轉(zhuǎn)換,這時(shí)true會(huì)變成number類型number(true)和1相加。
「TypeScript」
在TypeScript中,1+true這樣的代碼會(huì)在TypeScript中報(bào)錯(cuò),提示number類型不能和boolean類型進(jìn)行運(yùn)算。
何時(shí)檢查類型
「JavaScript」
在JavaScript中只有在程序運(yùn)行時(shí)才能檢查類型。類型也會(huì)存在隱式轉(zhuǎn)換,很坑。
「TypeScript」
在TypeScript中,在編譯時(shí)就會(huì)檢查類型,如果和預(yù)期的類型不符合直接會(huì)在編輯器里報(bào)錯(cuò)、爆紅
何時(shí)報(bào)告錯(cuò)誤
「JavaScript」
在JavaScript只有在程序執(zhí)行時(shí)才能拋出異常,JavaScript存在隱式轉(zhuǎn)換,等我們程序執(zhí)行時(shí)才能真正的知道代碼類型是否是預(yù)期的類型,代碼是不是有效。
「TypeScript」
在TypeScript中,當(dāng)你在編輯器寫代碼時(shí),如有錯(cuò)誤則會(huì)直接拋出異常,極大得提高了效率,也是方便。
五、TypeScript總共圍繞兩種模式展開(kāi)
顯式注解類型
舉個(gè)栗子
let name: string = "前端娛樂(lè)圈";
let age: number = 38;
let hobby: string[] = ["write code", "玩游戲"]
顯式注解類型就是,聲明變量時(shí)定義上類型(官方話語(yǔ)就是「聲明時(shí)帶上注解」),讓我們一看就明白,哦~,這個(gè)name是一個(gè)string類型。
推導(dǎo)類型
舉個(gè)栗子
let name = "前端娛樂(lè)圈"; // 是一個(gè)string類型
let age = 38; // 是一個(gè)number類型
let hobby = ["write code", "玩游戲"] // 是一個(gè)string數(shù)組類型
推導(dǎo)類型就是去掉顯示注解,系統(tǒng)自動(dòng)會(huì)識(shí)別當(dāng)前值是一個(gè)什么類型的。
六、安裝TypeScript && 運(yùn)行
typescript
全局安裝typescript環(huán)境。
npm i -g typescript
可是這只是安裝了typescript,那我們?cè)趺催\(yùn)行.ts文件呢,安裝完typescript我們就可以執(zhí)行tsc命令。
如:我們的文件叫做index.ts,直接在命令行執(zhí)行tsc index.ts即可。然后就可以看到在目錄下編譯出來(lái)一個(gè)index.js,這就是tsc編譯完的結(jié)果。
「index.ts」
const userName: string = "前端娛樂(lè)圈"
運(yùn)行tsc index.ts,你可以看見(jiàn)在index.ts的同級(jí)下又生成一個(gè)index.js,如下就是編譯的結(jié)果文件index.js。
var userName = "前端娛樂(lè)圈"
上面我們知道了運(yùn)行tsc命令就可以編譯生成一個(gè)文件,有的小伙伴覺(jué)得這樣太麻煩了,每次運(yùn)行只是編譯出來(lái)一個(gè)文件還不是運(yùn)行,還得用node index.js才可以運(yùn)行。不急我們接著往下看
ts-node
我們來(lái)看一下這個(gè)插件ts-node,這個(gè)插件可以直接運(yùn)行.ts文件,并且也不會(huì)編譯出來(lái).js文件。
npm i ts-node
// 運(yùn)行 ts-node index.ts
講到這里我們了解了「為什么要用TypeScript」和它的「優(yōu)缺點(diǎn)」以及它的「運(yùn)行工作方式」。
那么接下來(lái)步入TypeScript基礎(chǔ)知識(shí)的海洋啦~,follow me。
感覺(jué)有幫助的小伙伴可以關(guān)注一下:「前端娛樂(lè)圈」 公眾號(hào),謝謝啦~,每天更新一篇小技巧
七、基礎(chǔ)知識(shí)
1. 基礎(chǔ)靜態(tài)類型
在TypeScript中基礎(chǔ)類型跟我們JavScript中基礎(chǔ)類型是一樣的。只是有各別是Ts里面新出的。
1. number
const count: number = 18; // 顯示注解一個(gè)number類型
const count1 = 18; // 不顯示注解,ts會(huì)自動(dòng)推導(dǎo)出來(lái)類型
2. string
const str: string = "前端娛樂(lè)圈"; // 顯示注解一個(gè)string類型
const str1 = "蛙人"; // 不顯示注解,ts會(huì)自動(dòng)推導(dǎo)出來(lái)類型
3. boolean
const status: string = false; // 顯示注解一個(gè)string類型
const status1 = true; // 不顯示注解,ts會(huì)自動(dòng)推導(dǎo)出來(lái)類型
4. null
const value: null = null;
const value: null = undefined; // 這一點(diǎn)null類型可以賦值undefined跟在 js中是一樣的,null == undefined
5. undefined
const value: undefined = undefined;
const value: undefined = null; // 這一點(diǎn)null類型可以賦值undefined跟在 js中是一樣的,null == undefined
6. void
估計(jì)到這有一些小伙伴可能對(duì)void這個(gè)比較陌生,以為只有TypeScript才有的。其實(shí)不是哈,在我們JavaScript就已經(jīng)存在void關(guān)鍵字啦,它的意思就是無(wú)效的,有的小伙伴可能看見(jiàn)過(guò)早些項(xiàng)目里面<a href="javascript: void(0)">這是控制a標(biāo)簽的跳轉(zhuǎn)默認(rèn)行為。你不管怎么執(zhí)行void方法它都是返回undefined
那么在我們TypeScript中void類型是什么呢。它也是代表無(wú)效的,一般只用在「函數(shù)」上,告訴別人這個(gè)「函數(shù)」沒(méi)有返回值。
function fn(): void {} // 正確
function testFn(): void {
return 1; // 報(bào)錯(cuò),不接受返回值存在
}
function fn1(): void { return undefined} // 顯示返回undefined類型,也是可以的
function fn2(): void { return null} // 顯示返回null類型也可以,因?yàn)?nbsp;null == undefined
7. never
never「一個(gè)永遠(yuǎn)不會(huì)有值的類型」或者也可以說(shuō)「一個(gè)永遠(yuǎn)也執(zhí)行不完的類型」,代表用于不會(huì)有值,undefined、null也算做是值。一般這個(gè)類型就不會(huì)用到,也不用。大家知道這個(gè)類型就行。
const test: never = null; // 錯(cuò)誤
const test1: never = undefined // 錯(cuò)誤
function Person(): never { // 正確,因?yàn)樗姥h(huán)了,一直執(zhí)行不完
while(true) {}
}
function Person(): never { // 正確,因?yàn)檫f歸,永遠(yuǎn)沒(méi)有出口
Person()
}
function Person(): never { // 正確 代碼報(bào)錯(cuò)了,執(zhí)行不下去
throw new Error()
}
8. any
any這個(gè)類型代表「任何的」、「任意的」。希望大家在項(xiàng)目中,不要大片定義any類型。雖然它真的好使,那這樣我們寫TypeScript就沒(méi)有任何意義了。
let value: any = ""; // 正確
value = null // 正確
value = {} // 正確
value = undefined // 正確
9. unknown
unknown類型是我們TypeScript中第二個(gè)any類型,也是接受任意的類型的值。它的英文翻譯過(guò)來(lái)就是「未知的」,我們來(lái)看一下栗子
let value: unknown = ""
value = 1;
value = "fdsfs"
value = null
value = {}
那現(xiàn)在肯定有小伙伴疑惑,誒,那它unknown相當(dāng)于是any類型,那二者的區(qū)別是什么。我們來(lái)看一下
let valueAny: any = "";
let valueUnknown: unknown = "";
valueAny = "蛙人";
valueUnknown = "前端娛樂(lè)圈"
let status: null = false;
status = valueAny; // 正確
status = valueUnknown // 報(bào)錯(cuò),不能將unknown類型分配給null類型
我們來(lái)看一下上面的,為什么any類型就能被賦值成功,而unknown類型不行呢,從它倆的意義來(lái)上看,還是有點(diǎn)區(qū)別的,any任何的,任意的、unknown未知的。所以你給unknown類型賦值任何類型都沒(méi)關(guān)系,因?yàn)樗緛?lái)就是未知類型嘛。但是你如果把它的unknown類型去被賦值一個(gè)null類型,這時(shí)人家null這邊不干了,我不接受unknown類型。
說(shuō)白了一句話,別人不接受unknown類型,而unknown類型接受別人,哈哈哈哈。
2. 對(duì)象靜態(tài)類型
說(shuō)起對(duì)象類型,我們肯定都能想到對(duì)象包含{}、數(shù)組、類、函數(shù)
1. object && {}
其實(shí)這倆意思一樣,{}、object表示非原始類型,也就是除number,string,boolean,symbol,null或undefined之外的類型。
const list: object = {} // 空對(duì)象
const list1: object = null; // null對(duì)象
const list: object = [] // 數(shù)組對(duì)象
const list: {} = {}
list.name = 1 // 報(bào)錯(cuò) 不可更改里面的字段,但是可以讀取
list.toString()
2. 數(shù)組
const list: [] = []; // 定義一個(gè)數(shù)組類型
const list1: number[] = [1,2] // 定義一個(gè)數(shù)組,里面值必須是number
const list2: object[] = [null, {}, []] // 定義一個(gè)數(shù)組里面必須是對(duì)象類型的
const list3: Array<number> = [1,2,3] // 泛型定義數(shù)組必須是number類型,泛型我們待會(huì)講到
3. 類
// 類
class ClassPerson = {
name: "前端娛樂(lè)圈"
}
const person: ClassPerson = new Person();
person.xxx = 123; // 這行代碼報(bào)錯(cuò),因?yàn)楫?dāng)前類中不存在該xxx屬性
4. 函數(shù)
// 函數(shù)
const fn: () => string = () => "前端娛樂(lè)圈" // 定義一個(gè)變量必須是函數(shù)類型的,返回值必須是string類型
3. 函數(shù)類型注解
這里說(shuō)一下函數(shù)顯示注解和函數(shù)參數(shù)不會(huì)類型推導(dǎo)問(wèn)題。
1. 函數(shù)返回類型為number
function fn(a, b): number {
return a + b;
}
fn(1, 2)
2. 函數(shù)void
顯示注解為void類型,函數(shù)沒(méi)有返回值。
function fn(): void {
console.log(1)
}
3. 函數(shù)不會(huì)自動(dòng)類型推導(dǎo)
可以看到下面的函數(shù)類型,不會(huì)自動(dòng)類型推導(dǎo),我們實(shí)參雖然傳入的1和2,但是形參方面是可以接受任意類型值的,所以系統(tǒng)也識(shí)別不出來(lái)你傳遞的什么,所以這里得需要我們顯示定義注解類型。
function testFnQ(a, b) {
return a + b
}
testFnQ(1,2)

我們來(lái)改造一下。
function testFnQ(a:number, b:number) {
return a + b
}
testFnQ(1,2)

我們?cè)賮?lái)看一下參數(shù)對(duì)象顯示注解類型,也是在:號(hào)后面賦值每個(gè)字段類型即可。
function testFnQ(obj : {num: number}) {
return obj.num
}
testFnQ({num: 18})
4. 元組Tuple
元組用于表示一個(gè)已知數(shù)組的數(shù)量和類型的數(shù)組,定義數(shù)組中每一個(gè)值的類型,一般不經(jīng)常使用。
const arr: [string, number] = ["前端娛樂(lè)圈", 1]
const arr: [string, string] = ["前端娛樂(lè)圈", 1] // 報(bào)錯(cuò)
5. 枚舉Enum
Enum枚舉類型,可以設(shè)置默認(rèn)值,如果不設(shè)置則為索引。
enum color {
RED,
BLUE = "blue",
GREEN = "green"
}
// color["RED"] 0
// color["BLUE"] blue
像上面的color中RED沒(méi)有設(shè)置值,那么它的值則為0,如果BLUE也不設(shè)置的話那么它的值則是1,它們這里是遞增。如果設(shè)置值則是返回設(shè)置的值
「注意這里還有一個(gè)問(wèn)題,直接來(lái)上代碼」
通過(guò)上面學(xué)習(xí)我們知道了enum可以遞增值,也可以設(shè)置默認(rèn)值。但是有一點(diǎn)得注意一下,enum沒(méi)有json對(duì)象那樣靈活,enum不能在任意字段上設(shè)置默認(rèn)值。
比如下面栗子,RED沒(méi)有設(shè)置值,然后BLUE設(shè)置了默認(rèn)值,但是GREEN又沒(méi)有設(shè)置,這時(shí)這個(gè)GREEN會(huì)報(bào)錯(cuò)。因?yàn)槟愕诙€(gè)BLUE設(shè)置完默認(rèn)值,第三又不設(shè)置,這時(shí)代碼都不知道該咋遞增了,所以報(bào)錯(cuò)。還有一種方案就是你給BLUE可以設(shè)置一個(gè)數(shù)字值,這時(shí)第三個(gè)GREEN不設(shè)置也會(huì)跟著遞增,因?yàn)槎际?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">number類型。
// 報(bào)錯(cuò)
enum color {
RED,
BLUE = "blue",
GREEN
}
// good
enum color {
RED, // 0
BLUE = 4, // 4
GREEN // 5
}
比如enum枚舉類型還可以反差,通過(guò)value查key值。像我們json對(duì)象就是不支持這種寫法的。
enum color {
RED, // 0
BLUE = 4, // 4
GREEN // 5
}
console.log(color[4]) // BLUE
console.log(color[0]) // RED
5. 接口Interface
接口interface是什么,接口interface就是方便我們定義一處代碼,多處復(fù)用。接口里面也存在一些修飾符。下面我們來(lái)認(rèn)識(shí)一下它們吧。
1. 接口怎么復(fù)用
比如在講到這之前,我們不知道接口這東西,可能需要給對(duì)象定義一個(gè)類型的話,你可能會(huì)這樣做。
const testObj: { name: string, age: number } = { name: "前端娛樂(lè)圈", age: 18 }
const testObj1: { name: string, age: number } = { name: "蛙人", age: 18 }
我們用接口來(lái)改造一下。
interface Types {
name: string,
age: number
}
const testObj: Types = { name: "前端娛樂(lè)圈", age: 18 }
const testObj1: Types = { name: "蛙人", age: 18 }
可以看到使用interface關(guān)鍵字定義一個(gè)接口,然后賦值給這兩個(gè)變量,實(shí)現(xiàn)復(fù)用。
2. readonly修飾符
readonly類型,只可讀狀態(tài),不可更改。
interface Types {
readonly name: string,
readonly age: number
}
const testObj: Types = { name: "前端娛樂(lè)圈", age: 18 }
const testObj1: Types = { name: "蛙人", age: 18 }
testObj.name = "張三" // 無(wú)法更改name屬性,因?yàn)樗侵蛔x屬性
testObj1.name = "李四" // 無(wú)法更改name屬性,因?yàn)樗侵蛔x屬性
3. ?可選修飾符
可選修飾符以?定義,為什么需要可選修飾符呢,因?yàn)槿绻覀儾粚?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">可選修飾符,那interface里面的屬性都是必填的。
interface Types {
readonly name: string,
readonly age: number,
sex?: string
}
const testObj: Types = { name: "前端娛樂(lè)圈", age: 18}
4. extends繼承
我們的interface也是可以繼承的,跟「ES6」Class類一樣,使用extends關(guān)鍵字。
interface Types {
readonly name: string,
readonly age: number,
sex?: string
}
interface ChildrenType extends Types { // 這ChildrenType接口就已經(jīng)繼承了父級(jí)Types接口
hobby: []
}
const testObj: ChildrenType = { name: "前端娛樂(lè)圈", age: 18, hobby: ["code", "羽毛球"] }
5. propName擴(kuò)展
interface里面這個(gè)功能就很強(qiáng)大,它可以寫入不在interface里面的屬性。
interface Types {
readonly name: string,
readonly age: number,
sex?: string,
}
const testObj: Types = { name: "前端娛樂(lè)圈", age: 19, hobby: [] }
上面這個(gè)testObj這行代碼會(huì)爆紅,因?yàn)?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">hobby屬性不存在interface接口中,那么我們不存在的接口中的,還不讓人家寫了?。這時(shí)候可以使用「自定義」就是上面的propName。
interface Types {
readonly name: string,
readonly age: number,
sex?: string,
[propName: string]: any // propName字段必須是 string類型 or number類型。 值是any類型,也就是任意的
}
const testObj: Types = { name: "前端娛樂(lè)圈", age: 19, hobby: [] }
在運(yùn)行上面代碼,就可以看到不爆紅了~
6. Type
我們?cè)賮?lái)看一下Type,這個(gè)是聲明類型別名使的,別名類型只能定義是:基礎(chǔ)靜態(tài)類型、對(duì)象靜態(tài)類型、元組、聯(lián)合類型。
?注意:type別名不可以定義interface
?
type Types = string;
type TypeUnite = string | number
const name: typeUnite = "前端娛樂(lè)圈"
const age: typeUnite = 18
1. 那么type類型別名和interface接口有什么區(qū)別呢
1. type不支持interface聲明
type Types = number
type Types = string // 報(bào)錯(cuò), 類型別名type不允許出現(xiàn)重復(fù)名字
interface Types1 {
name: string
}
interface Types1 {
age: number
}
// interface接口可以出現(xiàn)重復(fù)類型名稱,如果重復(fù)出現(xiàn)則是,合并起來(lái)也就是變成 { name:string, age: number }
第一個(gè)Types類型別名type不允許出現(xiàn)重復(fù)名字,interface接口可以出現(xiàn)重復(fù)類型名稱,如果重復(fù)出現(xiàn)則是,合并起來(lái)也就是變 { name:string, age: number }
「再來(lái)看一下interface另一種情況」
interface Types1 {
name: string
}
interface Types1 {
name: number
}
可以看到上面兩個(gè)同名稱的interface接口,里面的屬性也是同名稱,但是類型不一樣。這第二個(gè)的Types1就會(huì)爆紅,提示:「后續(xù)聲明的接口,必須跟前面聲明的同名屬性類型必須保持一致」,把后續(xù)聲明的name它類型換成string即可。
2. type支持表達(dá)式 interface不支持
const count: number = 123
type testType = typeof count
const count: number = 123
interface testType {
[name: typeof count]: any // 報(bào)錯(cuò)
}
可以看到上面type支持表達(dá)式,而interface不支持
3. type 支持類型映射,interface不支持
type keys = "name" | "age"
type KeysObj = {
[propName in keys]: string
}
const PersonObj: KeysObj = { // 正常運(yùn)行
name: "蛙人",
age: "18"
}
interface testType {
[propName in keys]: string // 報(bào)錯(cuò)
}
7. 聯(lián)合類型
聯(lián)合類型用|表示,說(shuō)白了就是滿足其中的一個(gè)類型就可以。
const statusTest: string | number = "前端娛樂(lè)圈"
const flag: boolean | number = true
再來(lái)看一下栗子。我們用函數(shù)參數(shù)使用「聯(lián)合類型」看看會(huì)發(fā)生什么
function testStatusFn(params: number | string) {
console.log(params.toFixed()) // 報(bào)錯(cuò)
}
testStatusFn(1)
上面我們說(shuō)過(guò)了,函數(shù)參數(shù)類型不能類型自動(dòng)推導(dǎo),更何況現(xiàn)在用上「聯(lián)合類型」,系統(tǒng)更懵逼了,不能識(shí)別當(dāng)前實(shí)參的類型。所以訪問(wèn)當(dāng)前類型上的方法報(bào)錯(cuò)。
接下來(lái)帶大家看一些類型保護(hù),聽(tīng)著挺高級(jí),其實(shí)這些大家都見(jiàn)過(guò)。別忘了記得關(guān)注:「前端娛樂(lè)圈」 公眾號(hào)哦,嘻嘻
1. typeof
function testStatusFn(params: number | string) {
console.log(params.toFixed()) // 報(bào)錯(cuò)
}
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
// 報(bào)錯(cuò)
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 斷言
// 報(bào)錯(cuò)
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).name
console.log(res)
}
if ("age" in params) {
const res = (params as backEnd).age
console.log(res)
}
}
testStatusFn({age: 118})
8. 交叉類型
交叉類型就是跟聯(lián)合類型相反,它用&表示,交叉類型就是兩個(gè)類型必須存在。這里還用上面的「聯(lián)合類型」的栗子來(lái)看下。
interface frontEnd {
name: string
}
interface backEnd {
age: number
}
function testStatusFn(params: frontEnd & backEnd) {}
testStatusFn({age: 118, name: "前端娛樂(lè)圈"})
這里我們可以看到實(shí)參必須傳入兩個(gè)**接口(interface)**全部的屬性值才可以。「聯(lián)合類型」是傳入其中類型就可以。
「注意:我們的接口interface出現(xiàn)同名屬性」
interface frontEnd {
name: string
}
interface backEnd {
name: number
}
function testStatusFn(params: frontEnd & backEnd) {
console.log(params)
}
testStatusFn({name: "前端"})
上面我們兩個(gè)接口類型中都出現(xiàn)了同名屬性,但是類型不一樣,這時(shí)類型就會(huì)變?yōu)?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">never。

9. 泛型
泛型是TypeScript中最難理解的了,這里我盡量用通俗易懂的方式講明白。
function test(a: string | number, b: string | number) {
console.log(a, b)
}
test(1, "前端娛樂(lè)圈")
比如上面栗子,函數(shù)參數(shù)注解類型定義string和number,調(diào)用函數(shù)實(shí)參傳入也沒(méi)什么問(wèn)題,但是有個(gè)需求,就是實(shí)參我們「必須傳入同樣的類型」(傳入兩個(gè)number類型)。雖然上面這種「聯(lián)合類型」也可以實(shí)現(xiàn),但是如果我們要在加一個(gè)boolean類型,那么「聯(lián)合類型」還得在追加一個(gè)boolean,那這樣代碼太冗余了。
這時(shí)就需要用到「泛型」了,「泛型」是專門針對(duì)不確定的類型使用,并且靈活。泛型的使用大部分都是使用<T>,當(dāng)然也可以隨便使用,如:<Test>、<Custom>都可以。
function test<T>(a: T, b: T) {
console.log(a, b)
}
test<number>(1, "前端娛樂(lè)圈") // 調(diào)用后面跟著尖括號(hào)這就是泛型的類型,這時(shí)報(bào)錯(cuò),因?yàn)樵谡{(diào)用的使用類型是number,所以只能傳入相同類型的
test<boolean>(true, false)
test<string>("前端娛樂(lè)圈", "蛙人")
上面這使用「泛型」就解決了我們剛才說(shuō)的傳入同一個(gè)類型參數(shù)問(wèn)題,但是「泛型」也可以使用不同的參數(shù),可以把調(diào)用類型定義為<any>
function test<T>(a: T, b: T) {
console.log(a, b)
}
test<any>(1, "前端娛樂(lè)圈")
但是上面這種又有一種問(wèn)題,它可以傳入對(duì)象,但是如果我們只希望傳入number類型和string類型。那么我們「泛型」也給我們提供了**約束「類型。「泛型」使用extends進(jìn)行了」類型約束**,只能選擇string、number類型。
function test<T extends number | string, Y extends number | string>(a: T, b: Y) {
console.log(a, b)
}
test<number, string>(18, "前端娛樂(lè)圈")
test<string, number>("前端娛樂(lè)圈", 18)
這時(shí),傳入泛型時(shí)使用,逗號(hào)分隔,來(lái)定義每一個(gè)類型希望是什么。記住,只有我們不確定的類型,可以使用泛型。
10. 模塊
TypeScript也支持import和export這里大多數(shù)小伙伴都知道,這里都不多講啦。
// 導(dǎo)入
import xxx, { xxx } from "./xxx"
// 導(dǎo)出
export default {}
export const name = "前端娛樂(lè)圈"
如有不明白的小伙伴,可以看我以前文章 聊聊什么是CommonJs和Es Module及它們的區(qū)別
11. Class類
?以下這三個(gè)修飾符是在
?TypeScript類中才能使用,在JavaScript類中是不支持的。

1. public
public為類的公共屬性,就是不管在類的內(nèi)部還是外部,都可以訪問(wèn)該類中「屬性」及「方法」。默認(rèn)定義的「屬性」及「方法」都是public。
class Person {
name = "前端娛樂(lè)圈";
public age = 18;
}
const res = new Person();
console.log(res.name, res.age) // 前端娛樂(lè)圈 18
上面可以看到打印結(jié)果都能顯示出來(lái),name屬性沒(méi)有定義public公共屬性,所以類里面定義的「屬性」及「方法」默認(rèn)都是public定義。
2. private
private為類的私有屬性,只有在當(dāng)前類里面才能訪問(wèn),當(dāng)前類就是{}里面區(qū)域內(nèi)。在{}外面是不能訪問(wèn)private定義的「屬性」及「方法」的
class Person {
private name = "前端娛樂(lè)圈";
private age = 18;
}
const res = new Person();
console.log(res.name, res.age) // 這倆行會(huì)爆紅,當(dāng)前屬性為私有屬性,只能在類內(nèi)部訪問(wèn)
class Scholl extends Person {
getData() {
return this.username + "," + this.age
}
}
const temp = new Scholl()
console.log(temp.getData()) // 爆紅~,雖然繼承了Person類,但是private定義是只能在當(dāng)前類訪問(wèn),子類也不能訪問(wèn)。
3. protected
protected為類的保護(hù)屬性,只有在「當(dāng)前類」和「子類」可以訪問(wèn)。也就是說(shuō)用protected屬性定義的「子類」也可以訪問(wèn)。
class Person {
protected username = "前端娛樂(lè)圈";
protected age = 18;
}
const res = new Person();
console.log(res.name, res.age) // 這倆行會(huì)爆紅,當(dāng)前屬性為私有屬性,只能在類內(nèi)部訪問(wèn)
class Scholl extends Person {
getData() {
return this.username + "," + this.age
}
}
const temp = new Scholl()
console.log(temp.getData()) // 前端娛樂(lè)圈,18。可以正常訪問(wèn)父類的屬性
4. implements
implements關(guān)鍵字只能在class中使用,顧名思義,實(shí)現(xiàn)一個(gè)新的類,從父級(jí)或者從接口實(shí)現(xiàn)所有的屬性和方法,如果在PersonAll類里面不寫進(jìn)去接口里面已有的屬性和方法則會(huì)報(bào)錯(cuò)。
interface frontEnd {
name: string,
fn: () => void
}
class PersonAll implements frontEnd {
name: "前端娛樂(lè)圈";
fn() {
}
}
5. 抽象類
抽象類使用abstract關(guān)鍵字定義。abstract抽象方法不能實(shí)例化,如果,抽象類里面方法是抽象的,那么本身的類也必須是抽象的,抽象方法不能寫函數(shù)體。父類里面有抽象方法,那么子類也必須要重新該方法。
// 抽象類
abstract class Boss {
name = "秦";
call() {} // 抽象方法不能寫函數(shù)體
}
class A extends Boss {
call() {
console.log(this.name);
console.log("A")
}
}
class B extends Boss {
call() {
console.log("B")
}
}
new A().call()
該抽象類使用場(chǎng)景,比如A需求或者B需求正好需要一個(gè)公共屬性,然后本身還有一些自己的邏輯,就可以使用抽象類,抽象類只能在TypeScript中使用。
12. 命名空間namespace
我們學(xué)到現(xiàn)在可以看到,不知道小伙伴們發(fā)現(xiàn)沒(méi)有,項(xiàng)目中文件是不是不能有重復(fù)的變量(不管你是不是一樣的文件還是其它文件),否則就直接爆紅了。命名空間一個(gè)最明確的目的就是解決重名問(wèn)題。
命名空間使用namespace關(guān)鍵字來(lái)定義,來(lái)看栗子吧。
「index.ts」
namespace SomeNameSpaceName {
const q = {}
export interface obj {
name: string
}
}
上面這樣,就定義好了一個(gè)命名空間,可以看到變量q沒(méi)有寫export關(guān)鍵字,這證明它是內(nèi)部的變量,就算別的.ts文件引用它,它也不會(huì)暴露出去。而interface這個(gè)obj接口是可以被全局訪問(wèn)的。
「我們?cè)趧e的頁(yè)面訪問(wèn)當(dāng)前命名空間」
1. reference引入
/// <reference path="./index.ts" />
namespace SomeNameSpaceName {
export class person implements obj {
name: "前端娛樂(lè)圈"
}
}
2. import
export interface valueData {
name: string
}
import { valueData } from "./xxx.ts"
這時(shí)使用命名空間之后完全可以解決不同文件重名爆紅問(wèn)題。
13. tsConfig.json
這個(gè)tsconfig文件,是我們編譯ts文件,如何將ts文件編譯成我們的js文件。tsc -init這個(gè)命令會(huì)生成該文件出來(lái)哈。執(zhí)行完該命令,我們可以看到根目錄下會(huì)生成一個(gè)tsconfig.json文件,里面有一堆屬性。
那么我們?cè)趺磳?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">ts文件編譯成js文件呢,直接執(zhí)行tsc命令可以將根目錄下所有的.ts文件全部編譯成.js文件輸出到項(xiàng)目下。
更多配置文檔,請(qǐng)參考官網(wǎng)
{
// include: ["*.ts"] // 執(zhí)行目錄下所有的ts文件轉(zhuǎn)換成js文件
// include: ["index.ts"] // 只將項(xiàng)目下index.ts文件轉(zhuǎn)換為js文件
// files: ["index.ts"] // 跟include一樣,只執(zhí)行當(dāng)前數(shù)組值里面的文件,當(dāng)前files必須寫相對(duì)路徑
// exclude: ["index.ts"] // exclude就是除了index.ts不執(zhí)行,其它都執(zhí)行
compilerOptions: {
removeComments: true, // 去掉編譯完js文件的注釋
outDir: "./build", // 最終輸出的js文件目錄
rootDir: "./src", // ts入口文件查找
}
}
八、實(shí)用類型
最后來(lái)說(shuō)一下實(shí)用類型,TypeScript標(biāo)準(zhǔn)庫(kù)自帶了一些實(shí)用類型。這些實(shí)用類都是方便接口Interface使用。這里只列舉幾個(gè)常用的,更多實(shí)用類型官網(wǎng)
1. Exclude
從一個(gè)類型中排除另一個(gè)類型,只能是「聯(lián)合類型」,從TypesTest類型中排除UtilityLast類型。
「適用于:并集類型」
interface UtilityFirst {
name: string
}
interface UtilityLast {
age: number
}
type TypesTest = UtilityFirst | UtilityLast;
const ObjJson: Exclude<TypesTest, UtilityLast> = {
name: "前端娛樂(lè)圈"
}
2. Extract
Extract正好跟上面那個(gè)相反,這是選擇某一個(gè)可賦值的「聯(lián)合類型」,從TypesTest類型中只選擇UtilityLast類型。
「適用于:并集類型」
interface UtilityFirst {
name: string
}
interface UtilityLast {
age: number
}
type TypesTest = UtilityFirst | UtilityLast;
const ObjJson: Extract<TypesTest, UtilityLast> = {
age: 1
}
3. Readonly
把數(shù)組或?qū)ο蟮乃袑傩灾缔D(zhuǎn)換為只讀的。這里只演示一下對(duì)象栗子,數(shù)組同樣的寫法。
「適用于:對(duì)象、數(shù)組」
interface UtilityFirst {
name: string
}
const ObjJson: Readonly<UtilityFirst> = {
name: "前端娛樂(lè)圈"
}
ObjJson.name = "蛙人" // 報(bào)錯(cuò) 只讀狀態(tài)
4. Partial
把對(duì)象的所有屬性設(shè)置為選的。我們知道interface只要不設(shè)置?修飾符,那么對(duì)象都是必選的。這個(gè)實(shí)用類可以將屬性全部轉(zhuǎn)換為可選的。
「適用于:對(duì)象」
interface UtilityFirst {
name: string
}
const ObjJson: Partial<UtilityFirst> = {
}
5. Pick
Pick選擇對(duì)象類型中的部分key值,提取出來(lái)。第一個(gè)參數(shù)目標(biāo)值,第二個(gè)參數(shù)「聯(lián)合」key
「適用于:對(duì)象」
interface UtilityFirst {
name: string,
age: number,
hobby: []
}
const ObjJson: Pick<UtilityFirst, "name" | "age"> = {
name: "前端娛樂(lè)圈",
age: 18
}
6. Omit
Omit選擇對(duì)象類型中的部分key值,過(guò)濾掉。第一個(gè)參數(shù)目標(biāo)值,第二個(gè)參數(shù)「聯(lián)合」key
「適用于:對(duì)象」
interface UtilityFirst {
name: string,
age: number,
hobby: string[]
}
const ObjJson: Omit<UtilityFirst, "name" | "age"> = {
hobby: ["code", "羽毛球"]
}
7. Required
Required把對(duì)象所有可選屬性轉(zhuǎn)換成必選屬性。
「適用于:對(duì)象」
interface UtilityFirst {
name?: string,
age?: number,
hobby?: string[]
}
const ObjJson: Required<UtilityFirst> = {
name: "蛙人",
age: 18,
hobby: ["code"]
}
8. Record
創(chuàng)建一個(gè)對(duì)象結(jié)果集,第一個(gè)參數(shù)則是key值,第二個(gè)參數(shù)則是value值。規(guī)定我們只能創(chuàng)建這里面字段值。
「適用于:對(duì)象」
type IndexList = 0 | 1 | 2
const ObjJson: Record<IndexList, "前端娛樂(lè)圈"> = {
0: "前端娛樂(lè)圈",
1: "前端娛樂(lè)圈",
2: "前端娛樂(lè)圈"
參考資料
聊聊什么是CommonJs和Es Module及它們的區(qū)別: https://juejin.cn/post/6938581764432461854
[2]官網(wǎng): https://www.tslang.cn/docs/handbook/compiler-options.html
[3]官網(wǎng): https://www.typescriptlang.org/docs/handbook/utility-types.html
[4]了不起的 TypeScript 入門教程: https://juejin.cn/post/6844904182843965453
[5]TypeScript中文網(wǎng): https://www.tslang.cn/docs/handbook/basic-types.html
請(qǐng)你喝杯?? 記得三連哦~
1.閱讀完記得給?? 醬點(diǎn)個(gè)贊哦,有?? 有動(dòng)力
2.關(guān)注公眾號(hào)前端那些趣事,陪你聊聊前端的趣事
3.文章收錄在Github frontendThings 感謝Star?
