類(lèi)型推斷
基于賦值表達(dá)式推斷類(lèi)型的能力稱(chēng)之為“類(lèi)型推斷”。
在 TypeScript 中螃概,具有初始化值的變量矫夯、有默認(rèn)值的函數(shù)參數(shù)、函數(shù)返回值的類(lèi)型都可以根據(jù)上下文推斷出來(lái)吊洼。比如能根據(jù) return 語(yǔ)句推斷函數(shù)返回的類(lèi)型训貌。
let str = 'hello' // string
let num = 2 // number
const bar = 'bar' // 'bar' 類(lèi)型
// 推斷返回值類(lèi)型為 number
function add(a: number, b: number) {
return a + b
}
上下文推斷
通過(guò)變量所在的上下文環(huán)境推斷變量的類(lèi)型。
type Add = (a: number, b: number) => number
const add: Add = (a, b) => {
return a + b
}
// 推斷出 a, b, 函數(shù)返回值都是 number 類(lèi)型
字面量類(lèi)型
在 TypeScript 中冒窍,字面量不僅可以表示值递沪,還可以表示類(lèi)型,即所謂的字面量類(lèi)型综液。
目前款慨,TypeScript 支持 3 種字面量類(lèi)型:字符串字面量類(lèi)型、數(shù)字字面量類(lèi)型谬莹、布爾字面量類(lèi)型檩奠,對(duì)應(yīng)的字符串字面量、數(shù)字字面量附帽、布爾字面量分別擁有與其值一樣的字面量類(lèi)型埠戳,示例如下:
let num: 3 = 3
let bool: true = true
let str: 'xiaomi' = 'xiaomi'
字面量類(lèi)型是集合類(lèi)型的子類(lèi)型,它是集合類(lèi)型的一種更具體的表達(dá)蕉扮。
let specifiedStr: 'this is string' = 'this is string'
let str: string = 'any string'
specifiedStr = str // ts(2322) 類(lèi)型 '"string"' 不能賦值給類(lèi)型 'this is string'
str = specifiedStr // ok
實(shí)際上整胃,定義單個(gè)的字面量類(lèi)型并沒(méi)有太大的用處,真正的應(yīng)用場(chǎng)景是可以把多個(gè)字面量類(lèi)型組合成一個(gè)聯(lián)合類(lèi)型喳钟,用于描述擁有明確成員的集合爪模。
type Direction = 'up' | 'down'
通過(guò)使用字面量類(lèi)型組合的聯(lián)合類(lèi)型,可以限制函數(shù)的參數(shù)為指定的字面量類(lèi)型集合荚藻,然后編譯器會(huì)檢查參數(shù)是否是指定的字面量類(lèi)型集合里的成員屋灌。因此,相較于使用 string 類(lèi)型应狱,使用字面量類(lèi)型(組合的聯(lián)合類(lèi)型)可以將函數(shù)的參數(shù)限定為更具體的類(lèi)型缤骨。不僅提升程序的可讀性,還保證了函數(shù)的參數(shù)類(lèi)型橘茉。
類(lèi)型拓寬
Literal Widening(字面量類(lèi)型拓寬)
所有通過(guò) let 或 var 定義的變量、函數(shù)的形參写半、對(duì)象的非只讀屬性,如果滿足指定了初始值且未顯式添加類(lèi)型注解的條件尉咕,那么它們推斷出來(lái)的類(lèi)型就是指定的初始值字面量類(lèi)型拓寬后的類(lèi)型叠蝇,就是字面量類(lèi)型拓寬。
let str = 'this is string' // 類(lèi)型是 string
let strFun = (str = 'this is string') => str // 類(lèi)型是 (str?: string) => string;
const specifiedStr = 'this is string' // 類(lèi)型是 'this is string'
let str2 = specifiedStr // 類(lèi)型是 'string'
let strFun2 = (str = specifiedStr) => str // 類(lèi)型是 (str?: string) => string;
基于字面量類(lèi)型拓寬的條件年缎,可以通過(guò)如下所示代碼添加顯示類(lèi)型注解控制類(lèi)型拓寬行為悔捶。
const specifiedStr: 'this is string' = 'this is string' // 類(lèi)型是 '"this is string"'
let str2 = specifiedStr // 即便使用 let 定義,類(lèi)型是 'this is string'
除了字面量類(lèi)型拓寬之外单芜,TypeScript 對(duì)某些特定類(lèi)型值也有類(lèi)似 "Type Widening" (類(lèi)型拓寬)的設(shè)計(jì)蜕该。
比如對(duì) null 和 undefined 的類(lèi)型進(jìn)行拓寬,通過(guò) let洲鸠、var 定義的變量如果滿足未顯式聲明類(lèi)型注解且被賦予了 null 或 undefined 值堂淡,則推斷出這些變量的類(lèi)型是 any。
let x = null // 類(lèi)型拓寬成 any
let y = undefined // 類(lèi)型拓寬成 any
Type Narrowing (類(lèi)型縮邪峭蟆)
在 TypeScript 中绢淀,可以通過(guò)某些操作將變量的類(lèi)型由一個(gè)較為寬泛的集合縮小到相對(duì)較小、較明確的集合瘾腰,這就是類(lèi)型縮小更啄。
比如,使用類(lèi)型守衛(wèi)將形參的類(lèi)型從 any 縮小到明確的類(lèi)型:
const func = (foo: any) => {
if (typeof foo === 'string') {
return foo // string
} else if (typeof foo === 'number') {
return foo // number
}
return null
}
還可以使用類(lèi)型守衛(wèi)將聯(lián)合類(lèi)型縮小到明確的子類(lèi)型:
const func = (foo: string | number) => {
if (typeof foo === 'string') {
return foo // string
} else {
return foo // number
}
}
也可以通過(guò)字面量類(lèi)型等值判斷(===)或其他控制流語(yǔ)句(包括但不限于 if居灯、三目運(yùn)算符祭务、switch 分支)將聯(lián)合類(lèi)型收斂為更具體的類(lèi)型,如下代碼所示:
type Color = 'green' | 'red' | 'gray'
const getStatus = (status: Color) => {
if (status === 'green') return status
if (status === 'red') return status
if (status === 'gray') return status
}
大致總結(jié):
let 聲明的簡(jiǎn)單類(lèi)型字面量會(huì)拓寬類(lèi)型怪嫌,const 聲明的簡(jiǎn)單類(lèi)型字面量會(huì)收窄义锥,const 聲明的對(duì)象變量會(huì)自動(dòng)推斷對(duì)應(yīng)的類(lèi)型,可以用 as const 收窄岩灭,讓每一個(gè) key 都是固定類(lèi)型(只讀【readonly】的值)
類(lèi)型謂詞(is)
TypeScript 中拌倍,函數(shù)還支持另外一種特殊的類(lèi)型描述:
function isString(s): s is string {
// 類(lèi)型謂詞
return typeof s === 'string'
}
function isNumber(n: number) {
return typeof n === 'number'
}
function operator(x: unknown) {
if (isString(x)) {
// ok x 類(lèi)型縮小為 string
}
if (isNumber(x)) {
// ts(2345) unknown 不能賦值給 number
}
}
分布式有條件類(lèi)型
有條件的類(lèi)型會(huì)以一個(gè)條件表達(dá)式進(jìn)行類(lèi)型關(guān)系檢測(cè),從而在兩種類(lèi)型中選擇其一噪径,使用關(guān)鍵字 extends 配合三目運(yùn)算符:
T extends U ? X : Y
如果 extends
前面是簡(jiǎn)單的條件判斷柱恤,則直接判斷前面的類(lèi)型是否可分配給后面的類(lèi)型。
若 extends 前面的類(lèi)型是泛型找爱,且泛型傳入的是聯(lián)合類(lèi)型時(shí)梗顺,則會(huì)依次判斷該聯(lián)合類(lèi)型的所有子類(lèi)型是否可分配給 extends 后面的類(lèi)型(是一個(gè)分發(fā)的過(guò)程)。
即 T extends U ? X : Y车摄,若 T 的類(lèi)型為 A | B | C寺谤,則會(huì)被解析為(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)仑鸥。
// type A1 = 1
type A1 = 'x' extends 'x' ? 1 : 2
// type A2 = 2
type A2 = 'x' | 'y' extends 'x' ? 1 : 2
// type A3 = 1 | 2
type P<T> = T extends 'x' ? 1 : 2
type A3 = P<'x' | 'y'>
type StrOrNum<T> = T extends string ? T : number
type Str = StrOrNum<string>
// null undefined
type Str<T> = T extends null | undefined ? T : never
type Strs = StrNull<string> // never
type StrNull<T> = T extends string | null | undefined ? T : never
type Strs = StrNull<string | number> // string | number
如果用于簡(jiǎn)單的條件判斷,則是直接判斷前面的類(lèi)型是否可分配給后面的類(lèi)型
const 斷言
interface Info {
readonly name: string
readonly age: number
}
const info: Info = {
name: 'hack',
age: 23
}
// info.age = 4 // Error: ts(2540) age 為只讀屬性
// 等價(jià)于
// 使用 const 斷言
const obj = {
name: 'jack',
age: 34
} as const
obj.age = 5 // Error: ts(2540) age 為只讀屬性
還可以將數(shù)組轉(zhuǎn)成 readonly 元組
const arr = ['str', 12] as const
arr[1] = 55 // 無(wú)法為“1”賦值变屁,因?yàn)樗侵蛔x屬性眼俊。ts(2540)
typeof 操作符
typeof 操作符用來(lái)獲取一個(gè)變量或?qū)ο蟮念?lèi)型。也就是說(shuō) TS 對(duì) typeof 操作符做了擴(kuò)展:
type Person = {
name: string
age: number
}
const person: Person = {
name: 'xiaomi',
age: 18
}
type Human = typeof person
// type Human = {
// name: string
// age: number
// }
keyof
對(duì)于任何類(lèi)型 T粟关,keyof T 的結(jié)果為該類(lèi)型上所有公共屬性名的字符串或數(shù)字的組成的聯(lián)合類(lèi)型疮胖。
type Point = { x: number; 1: number }
type Keys = keyof Point
/**
Keys 的類(lèi)型為
type Keys = 'x' | 1
*/
如果該類(lèi)型具有 string 或 number 索引簽名,keyof 則將返回這些類(lèi)型:
type Arrayish = { [n: number]: unknown }
type A = keyof Arrayish
// type A = number
type Mapish = { [k: string]: boolean }
type M = keyof Mapish
// type M = string | number
M 類(lèi)型是 string | number闷板,是因?yàn)?JavaScript 對(duì)象鍵始終會(huì)強(qiáng)制轉(zhuǎn)換為字符串澎灸,因此 obj[0] === obj["0"]
根據(jù) TS 中對(duì)象的值或鍵創(chuàng)建聯(lián)合類(lèi)型
從對(duì)象的值或鍵創(chuàng)建聯(lián)合類(lèi)型:
- 用
as const
將對(duì)象的屬性設(shè)置為readonly - 用 `keyof typeof1 獲取對(duì)象中鍵的類(lèi)型
- 使用鍵來(lái)獲取值的并集
// ??? const obj: {readonly name: "Bobby Hadz"; readonly country: "Chile";}
const obj = {
name: 'Bobby Hadz',
country: 'Chile',
} as const;
// ??? type UValues = "Bobby Hadz" | "Chile"
type UValues = (typeof obj)[keyof typeof obj];
// ??? type UKeys = "name" | "country"
type UKeys = keyof typeof obj;
- 使用as const聲明對(duì)象是一個(gè)只讀的
- 使用typeof 獲取對(duì)象的類(lèi)型
- 使用keyof typeof 獲取對(duì)象key的聯(lián)合
T[K] 索引訪問(wèn)操作符
T[keyof K]的方式,獲取到的是 T 中的 key 且同時(shí)存在于 K 時(shí)的類(lèi)型組成的聯(lián)合類(lèi)型蛔垢。
如果[]中的 key 有不存在 T 中击孩,則是 any迫悠;因?yàn)?TS 也不知道該 key 最終是什么類(lèi)型鹏漆,所以是 any;且也會(huì)報(bào)錯(cuò)创泄。
interface Eg {
name: string
readonly age: number
}
// string
type V1 = Eg['name']
// string | number
type V2 = Eg['name' | 'age']
// any
type V3 = Eg['name' | 'age2222'] // Error
// string | number
type V4 = Eg[keyof Eg1]
映射類(lèi)型
typeScript 提供了從舊類(lèi)型中創(chuàng)建新類(lèi)型的一種方式 — 映射類(lèi)型艺玲。 在映射類(lèi)型里,新類(lèi)型以相同的形式去轉(zhuǎn)換舊類(lèi)型里每個(gè)屬性鞠抑。
需要使用關(guān)鍵字in
:
interface Ra {
name: string
age: number
}
type ReadonlyRa<T> = {
readonly [K in keyof T]: T[K]
}
const ra: ReadonlyRa<Rabbit> = {
name: 'jack',
age: 3
}
! 非空斷言操作符
x!
將從 x
值域中排除 null
和 undefined
饭聚。比如給某一個(gè)元素綁定點(diǎn)擊事件
// const ele: HTMLElement = document.getElementById('#app')
// 因?yàn)閑le 可能的值為 null 或 DOM節(jié)點(diǎn)。但是我們確定 ele 一定不為null搁拙,則通過(guò)添加 `!`非空斷言運(yùn)算符
const ele: HTMLElement = document.getElementById('#app')!
ele.addEventListener('click', (e: Event) => {
//...
})
?? 空值合并運(yùn)算符
||
或 運(yùn)算符是左側(cè)為 falsy
時(shí)秒梳,返回右側(cè)值。
null || 3 // 3
undefined || 3 // 3
false || 3 // 3
'' || 3 // 3
;-0 || 3 // 3
0 || 3 // 3
0n || 3 // 3
NaN || 3 // 3
而 ??
運(yùn)算符是只有左側(cè)的值為 null 或 undefined
時(shí)箕速,才返回右側(cè)的操作數(shù)酪碘,否則返回左側(cè)的值。
null ?? 3 // 3
undefined ?? 3 // 3
false ?? 3 // false
注意:不能與 &&
或 ||
操作符共用
空值合并運(yùn)算符 ??
不能直接與 &&
和 ||
操作符組合使用盐茎。這種情況下會(huì)拋出 SyntaxError兴垦。
// '||' and '??' operations cannot be mixed without parentheses.(5076)
null || undefined ?? 'foo' // raises a SyntaxError
// '&&' and '??' operations cannot be mixed without parentheses.(5076)
true && undefined ?? 'foo' // raises a SyntaxError
但當(dāng)使用括號(hào)來(lái)顯式表明優(yōu)先級(jí)時(shí)是可行的,比如:
;(null || undefined) ?? 'foo' // 'foo'
內(nèi)置工具類(lèi)型
聯(lián)合類(lèi)型
Exclude<T, U>
Exclude<T, U> 提取存在于 T字柠,但不存在于 U 的類(lèi)型組成的聯(lián)合類(lèi)型探越,通俗的說(shuō):從聯(lián)合類(lèi)型中去除指定的類(lèi)型。
type Keys = Exclude<'key1' | 'key2', 'key2'>
// type Keys = 'key1'
實(shí)現(xiàn):
type Exclude<T, U> = T extends U ? never : T
never 表示一個(gè)不存在的類(lèi)型窑业,而且與其他類(lèi)型聯(lián)合后是沒(méi)有 never 的
Extract<T, U>
Extract<T, U> 提取聯(lián)合類(lèi)型 T 和聯(lián)合類(lèi)型 U 的所有交集钦幔。通俗地說(shuō):從聯(lián)合類(lèi)型中提取指定的類(lèi)型。
type Keys = Extract<'key1' | 'key2', 'key1'>
// type Keys = 'key1'
實(shí)現(xiàn):
type Extract<T, U> = T extends U ? T : never
NonNullable
NonNullable 的作用是從聯(lián)合類(lèi)型中去除 null 或者 undefined 的類(lèi)型常柄。
type T = NonNullable<string | number | undefined | null> // => string | number
實(shí)現(xiàn):
type NonNullable<T> = Exclude<T, null | undefined>
// 或者
type NonNullable<T> = T extends null | undefined ? never : T
Record
Record<K, T> 用來(lái)構(gòu)造一個(gè)類(lèi)型节槐,屬性為聯(lián)合類(lèi)型中的每個(gè)子類(lèi)型搀庶,屬性類(lèi)型為 T。
type Eg = Record<'a' | 'b', string>
/**
type Eg = {
a: string
b: string
}
/*
實(shí)現(xiàn):
type Record<K extends keyof any, T> = {
[P in K]: T
}
注意:keyof any
得到的是 string | number | symbol
铜异,原因在于 key
的類(lèi)型只能為 string | number | symbol
操作接口類(lèi)型
interface Person {
name: string
age?: number
weight?: number
}
Partial<T>
將類(lèi)型 T 所有屬性設(shè)置成可選的
type PartialPerson = Partial<Person>
// 相當(dāng)于
interface PartialPerson {
name?: string
age?: number
weight?: number
}
實(shí)現(xiàn):
type Partial<T> = {
[P in keyof T]?: T[P]
}
將指定的 key 設(shè)置成可選的
type PratialOptionalPerson = PratialOptional<Person, 'name'>
//相當(dāng)于
interface PratialOptionalPerson {
name?: string
}
實(shí)現(xiàn):
type PratialOptional<T, K extends keyof T> = {
[P in K]?: T[K]
}
Required
將給定類(lèi)型的所有屬性變?yōu)楸剡x項(xiàng)
interface Person {
name: string
age?: number
weight?: number
}
type RequiredPerson = Required<Person>
// 相當(dāng)于
interface RequiredPerson {
name: string
age: number
weight: number
}
Readonly
將類(lèi)型 T 的所有屬性設(shè)置成只讀的
type ReadonlyPerson = Readonly<Person>
// 相當(dāng)于
interface ReadonlyPerson {
readonly name: string
readonly age?: number
readonly weight?: number
}
實(shí)現(xiàn):
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
將指定的 key 設(shè)置成只讀的:
type ReadonlyOptional<T, K extends keyof T> = {
readonly [P in K]: T[P]
}
Pick
選取一組屬性哥倔,組成新的類(lèi)型
type NewPerson = Pick<Person, 'name' | 'age'>
// 相當(dāng)于
interface NewPerson {
name: string
age?: number
}
實(shí)現(xiàn):
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
Omit<T, K>
與 Pick 相反,Omit 是去除指定的屬性之后返回的新類(lèi)型
type NewPerson = Omit<Person, 'weight'>
// 相當(dāng)于
interface NewPerson {
name: string
age?: number
}
實(shí)現(xiàn):
- 方式 1
type Omit = Pick<T, Exclude<keyof T, K>>
- 方式 2
type Omit<T, K extends keyof any> = {
[P in Exclude<keyof T, K>]: T[P]
}
以上操作接口的工具類(lèi)型都使用了映射類(lèi)型揍庄。通過(guò)映射類(lèi)型咆蒿,可以對(duì)原類(lèi)型的屬性進(jìn)行重新映射組成新的的類(lèi)型。
同態(tài)和非同態(tài)
- Partial蚂子、Readonly 和 Pick 都屬于同態(tài)的沃测,也就是其實(shí)現(xiàn)需要輸入類(lèi)型 T 來(lái)拷貝屬性,因此屬性修飾符(readonly食茎、?)都會(huì)被拷貝
type AP = Pick<{ readonly a?: string; b: number }, 'a'>
/**
type AP = {
readonly a?: string | undefined
}
*/
以上可以看出屬性修飾符 readonly 和 ? 都被拷貝了蒂破。
- Record 是非同態(tài)的,不需要拷貝屬性别渔,因此不會(huì)拷貝屬性修飾符
為什么 Pick 拷貝了屬性附迷,而 Record 沒(méi)有拷貝?
因?yàn)?Pick 的實(shí)現(xiàn)中哎媚,P in K
(本質(zhì)是 P in keyof T)喇伯,T 為輸入的類(lèi)型,而 keyof T 則遍歷了輸入類(lèi)型拨与;而 Record 的實(shí)現(xiàn)中稻据,并沒(méi)有遍歷所有輸入的類(lèi)型,K 只是約束為 keyof any 的子類(lèi)型即可买喧。
Pick捻悯、Partial、readonly 這幾個(gè)類(lèi)型工具淤毛,無(wú)一例外今缚,都是使用到了 keyof T 來(lái)輔助拷貝傳入類(lèi)型的屬性。
函數(shù)類(lèi)型
Parameters
獲取函數(shù)的參數(shù)類(lèi)型钱床,將每個(gè)參數(shù)類(lèi)型放在一個(gè)元組中
type T0 = Parameters<() => void> // []
type T1 = Parameters<(x: number, y?: string) => void> // [x: number, y?: string]
實(shí)現(xiàn):
type Parameters<T extends (...args: any) => any> = T extends (
...args: infer P
) => any
? P
: never
ReturnType
用來(lái)獲取函數(shù)的返回類(lèi)型
type T0 = ReturnType<() => void> // => void
type T1 = ReturnType<() => string> // => string
實(shí)現(xiàn):
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any
ThisParameterType
用來(lái)獲取函數(shù)的 this 參數(shù)類(lèi)型
type T = ThisParameterType<(this: Number, x: number) => void> // Number
實(shí)現(xiàn):
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any
? U
: unknown
OmitThisParameter
用來(lái)去除函數(shù)類(lèi)型中的 this 類(lèi)型荚斯。如果傳入的函數(shù)類(lèi)型沒(méi)有顯式聲明 this 類(lèi)型,那么返回的仍是原來(lái)的函數(shù)類(lèi)型
type T = OmitThisParameter<(this: Number, x: number) => string> // (x: number) => string
實(shí)現(xiàn):
type OmitThisParameter<T> = unknown extends ThisParameterType<T>
? T
: T extends (...args: infer A) => infer R
? (...args: A) => R
: T
字符串類(lèi)型
Uppercase
字符串字面量轉(zhuǎn)換成大寫(xiě)字母
type T = Uppercase<'Hello'> // => 'HELLO'
Lowercase
字符串字面量轉(zhuǎn)換成小寫(xiě)字母
type T = Lowercase<'HElO'> // => 'hello'
Capitalize
字符串的第一個(gè)字母轉(zhuǎn)為大寫(xiě)字母
type T = Capitalize<'hello'> // => 'Hello'
Uncapitalize
字符串第一個(gè)字母轉(zhuǎn)成小寫(xiě)
type T = Uncapitalize<'Hello'> // => 'hello'