盤點(diǎn) TypeScript 中我們常用的那些神奇符號(hào)
回復(fù)交流,加入前端編程面試算法每日一題群
?. 可選鏈(Optional Chaining)
ES11(ES2020)新增的特性,TypeScript 3.7 支持了這個(gè)特性
我們在 為什么要使用 TypeScript?TypeScript 相對于 JavaScript 的優(yōu)勢是什么?中提到 TypeScript 與標(biāo)準(zhǔn)同步發(fā)展,并推進(jìn)了很多 ECMAScripts 語法提案,比如可選鏈操作符(
?.)、空值合并操作符(??)、Throw 表達(dá)式、正則匹配索引等,所以,這里介紹的符號(hào)大部分都是 ECMAScripts 規(guī)范的,TypeScript 特有的只有?:、!、&、|
可選鏈可讓我們在查詢具有多層級(jí)的對象時(shí),不再需要進(jìn)行冗余的各種前置校驗(yàn):
var info = user && user.info
又或是這種
var age = user && user.info && user.info.getAge && user.info.getAge()
很容易命中 Uncaught TypeError: Cannot read property...
用了 Optional Chaining ,上面代碼會(huì)變成
var info = user?.info
var age = user?.info?.getAge?.()
TypeScript 在嘗試訪問 user.info 前,會(huì)先嘗試訪問 user ,user 既不是 null 也不是 undefined 才會(huì)繼續(xù)往下訪問,如果user是 null 或者 undefined,則表達(dá)式直接返回 undefined
即可選鏈?zhǔn)且环N先檢查屬性是否存在,再嘗試訪問該屬性的運(yùn)算符 ( ?. )
目前,可選鏈支持以下語法操作:
obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)
?? 空值合并運(yùn)算符(Nullish coalescing Operator)
ES12(ES2021)新增的特性,TypeScript 3.7 支持了這個(gè)特性,當(dāng)左側(cè)的操作數(shù)為 null 或者 undefined 時(shí),返回其右側(cè)操作數(shù),否則返回左側(cè)操作數(shù)。
// {
// "level": null
// }
var level1 = user.level ?? '暫無等級(jí)' // level1 -> '暫無等級(jí)'
var level2 = user.other_level ?? '暫無等級(jí)' // level1 -> '暫無等級(jí)'
與邏輯或操作符(||) 不同,|| 會(huì)在左側(cè)操作數(shù)為 falsy 值(例如,'' 或 0)時(shí)返回右側(cè)操作數(shù)。也就是說,如果使用 || 來為某些變量設(shè)置默認(rèn)值,可能會(huì)遇到意料之外的行為:
// {
// "level": 0
// }
var level1 = user.level || '暫無等級(jí)' // level1 -> 暫無等級(jí)
var level2 = user.level ?? '暫無等級(jí)' // level2 -> 0
?: 可選參數(shù)和屬性
TypeScript 特有的,在 TypeScript 2.0 支持了這個(gè)特性,可選參數(shù)和屬性會(huì)自動(dòng)把 undefined 添加到他們的類型中,即使他們的類型注解明確不包含 undefined 。例如,下面兩個(gè)類型是完全相同的:
// 使用--strictNullChecks參數(shù)進(jìn)行編譯
type T1 = (x?: number) => string // x的類型是 number | undefined
type T2 = (x?: number | undefined) => string // x的類型是 number | undefined
在TypeScript里,我們使用 ?: 最多的情況是在接口中,通常:
interface Point {
x: number;
y: number;
}
let point: Point
point = {
x: 1,
y: 2
}
其中 point 中的兩個(gè)屬性 x 、 y 都是必須的,如果賦值時(shí)缺少任意一個(gè)就會(huì)報(bào)錯(cuò):
point = {
x: 1
}
// Property 'y' is missing in type '{ x: number; }' but required in type 'Point'.
但接口里的屬性不全都是必需的。有些是只在某些條件下存在,或者根本不存在。所以,這里就需要可選屬性( ?. ),即屬性是可選的
interface Point {
x: number;
y: number;
z?: number; // 可選屬性
}
let point: Point
point = {
x: 1,
y: 2
}
在 TypeScript 有兩個(gè)內(nèi)置的工具泛型可以幫助我們處理接口的可選操作:
-
Partial:把接口中的所有屬性變成可選的 -
Required:將接口中所有可選的屬性改為必須的
Partial
Partial 的作用即把類型中的所有屬性變成可選的
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
}
例如:
interface Point {
x: number;
y: number;
}
type PartialPoint = Partial<Point>
// PartialPoint 相當(dāng)于:
// type PartialPoint = {
// x?: number;
// y?: number;
// }
// 所有屬性均可選
它具體是如何實(shí)現(xiàn)的喃?
首先了解 keyof ,keyof 指的是把我們一個(gè)對象里面的鍵值對里的鍵( key )一一羅列出來,并把它們聯(lián)合起來形成一種聯(lián)合類型:
interface Point {
x: number;
y: number;
}
type PointKeys = keyof Point // "x" | "y"
in 是遍歷的作用,P in keyof T 把 keyof T 進(jìn)行一個(gè)個(gè)遍歷并且每個(gè)都單獨(dú)拿出來生成新的 "鍵值對"
所以:
// Partial 語法
// type Partial<T> = {
// [P in keyof T]?: T[P];
// };
interface Point {
x: number;
y: number;
}
type PartialPoint = Partial<Point>
// 第一步↓
type PartialPoint = {
[P in 'x' | 'y']?: Point[P];
}
// 第二步↓
type PartialPoint = {
x?: Point["x"];
y?: Point["y"];
}
// 最終↓
type PartialPoint = {
x?: number;
y?: number;
}
因此,實(shí)現(xiàn)了 Partial 的效果
Required
Required 的作用剛好與 Partial 相反,就是將接口中所有可選的屬性改為必須的,區(qū)別就是把 Partial 里面的 ? 替換成了 -?
type Required<T> = {
[P in keyof T]-?: T[P];
}
例如:
interface Point {
x?: number;
y?: number;
}
type RequiredPoint = Required<Point>
// RequiredPoint 相當(dāng)于:
// type RequiredPoint = {
// x: number;
// y: number;
// }
// 所有屬性均必須
! 非空斷言操作符
TypeScript 特有的,在 TypeScript 2.0 支持了這個(gè)特性,在上下文中當(dāng)類型檢查器無法斷定類型時(shí),一個(gè)新的后綴表達(dá)式操作符 ! 可以用于斷言操作對象是非 null 和非 undefined 類型的。具體而言,運(yùn)算 x! 產(chǎn)生一個(gè)不包含 null 和 undefined 的 x 的值。
function sayHello(hello: string | undefined) {
const hi1 = hello!.toLowerCase() // OK
const hi2 = hello.toLowerCase() // Error: Object is possibly 'undefined'
}
僅僅只是騙過了編譯器,當(dāng)你調(diào)用 sayHello() 依然會(huì)報(bào)錯(cuò),這樣使用是因?yàn)槟阋呀?jīng)斷言了 hello 一定是 string
let root: (HTMLElement | null) = document.getElementById('root')
// 非空斷言操作符--> 這樣寫只是為了騙過編譯器,防止編譯的時(shí)候報(bào)錯(cuò),但打包后的代碼可能還是會(huì)報(bào)錯(cuò)
root!.style.color = 'red'
非空斷言操作符 與 類型守衛(wèi)
類型守衛(wèi)用于確保該類型在一定的范圍內(nèi),常用 typeof 、 instanceof 、in 等
function sayHello(hello: string | undefined) {
if(typeof hello === 'string') {
const hi = hello.toLowerCase()
}
}
但如果你這樣寫:
function sayHello(hello: string | undefined) {
const isSay = typeof hello === 'string'
if(isSay) {
const hi1 = hello.toLowerCase() // Error: Object is possibly 'undefined'.
const hi2 = hello!.toLowerCase() // OK
}
}
就會(huì)報(bào)錯(cuò),即使 isSay 被分配到了類型守衛(wèi)值,TypeScript 也只會(huì)丟失該信息。所以我們一般會(huì) const hi = hello!.toLowerCase() 加上非空斷言操作符
但 TypeScript 4.4 RC 會(huì)修復(fù)這個(gè)問題,如果你遇到這個(gè)問題,可升級(jí)到 TypeScript 4.4 版本后
_ 數(shù)字分隔符(Numeric separators)
ES12(ES2021)新增的特性,TypeScript 2.7 就已經(jīng)支持了這個(gè)特性, 這個(gè)特性允許用戶在數(shù)字之間使用下劃線_來對數(shù)字分組。
const million = 1_000_000
const phone = 173_1777_7777
const bytes = 0xFF_0A_B3_F2
const word = 0b1100_0011_1101_0001
需要注意的是以下函數(shù)是不支持分隔符:
-
Number() -
parseInt() -
parseFloat()
const million = '1_234_567'
Number(million)
// NaN
parseInt(million)
// 1
parseFloat(million)
// 1
** 指數(shù)操作符
ES7(ES2016)新增的特性
2**5 // 32
& 交叉類型(Intersection Types)
在 TypeScript 中,交叉類型是將多個(gè)類型合并為一個(gè)類型,我們可以通過 & 把現(xiàn)有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性
type PointX = {
x: number;
}
type Point = PointX & {
y: number;
}
let point: Point = {
x: 1,
y: 2
}
如果多個(gè)類型中存在相同的屬性喃?
type PointX = {
x: number;
z: string;
}
type Point = PointX & {
y: number;
z: number;
}
let point: Point = {
x: 1,
y: 2,
z: 3, // Type 'number' is not assignable to type 'never'.
}
這里 z 為什么會(huì)是 never 類型喃?因?yàn)?string & number 的值是永不存在的值,即 never
type PointX = {
x: number;
z: {x: string};
}
type Point = PointX & {
y: number;
z: {z: number};
}
let point: Point = {
x: 1,
y: 2,
z: {
x: '1',
z: 2
},
}
而這樣是可以的,所以,即多個(gè)類型合并為一個(gè)交叉類型時(shí),如果多個(gè)類型間存在同名基礎(chǔ)類型屬性時(shí),合并后的同名基礎(chǔ)類型屬性為 never ,如果同名屬性均為非基礎(chǔ)類型,則可以成功合并
| 聯(lián)合類型(Union Types)
聯(lián)合類型表示一個(gè)值可以是幾種類型之一,用豎線( |)分隔每個(gè)類型,所以 number | string | boolean 表示一個(gè)值可以是 number, string,或 boolean
let user: string | number | boolean = 'an'
聯(lián)合類型通常與 null 或 undefined 一起使用:
const helloName = (name: string | undefined) => {
/* ... */
};
你也可以這么用:
type Hello = 'say' | 'kiss' | 'smile';
來源 | https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/529
最后
號(hào)內(nèi)回復(fù):
120 套模版
