TS學(xué)習(xí)筆記(八):高級類型

交叉類型

交叉類型將多個類型合并為一個類型称杨,相當(dāng)于新類型具有這多個類型的所有特性,相當(dāng)于是一種的操作牵署,通常在使用混入(mixin)的場合使用交叉類型霜瘪,交叉類型的形式如:

T & U

示例:

interface IPerson {
  name: string;
  age: number;
}

interface IMan {
  love: string;
  age: number;
}

let mixin: <T, U>(age: T, love: U) => T = function<T, U>(age: T, love: U): T & U {
  return Object.assign(age, love);
}

let me = mixin<IPerson, IMan>({name: 'funlee', age: 10}, { love: 'TS', age: 18});

console.log(me); // {name: "funlee", age: 18, love: 'TS}

聯(lián)合類型

聯(lián)合類型用于限制傳入的值的類型只能是 | 分隔的每個類型,如:number | string | boolean 表示一個值的類型只能是 number食店、string渣淤、boolean 中的一種。
此外吉嫩,如果一個值是聯(lián)合類型价认,那么我們只能訪問它們中共有的部分(共有的屬性與方法),即相當(dāng)于一種的關(guān)系自娩,如:

interface IPerson {
  name: string;
  age: number;
}

interface IMan {
  love: string;
  age: number;
}

let me: IPerson | IMan;
me = {
  name: 'funlee',
  age: 18,
  love: 'TS'
}
console.log(me); // {name: "funlee", age: 18, love: "TS"}
console.log(me.name); // ERROR
console.log(me.age); // 18
console.log(me.love); // ERROR

類型保護(hù)與區(qū)分類型

聯(lián)合類型可以讓一個值可以為不同的類型刻伊,但隨之帶來的問題就是訪問非共同方法時會報錯。那么該如何區(qū)分值的具體類型椒功,以及如何訪問共有成員捶箱?

1.使用類型斷言

interface IPerson {
  name: string;
  age: number;
}

interface IMan {
  love: string;
  age: number;
}

let me: IPerson | IMan;
me = {
  name: 'funlee',
  age: 18,
  love: 'TS'
}
console.log(me); // {name: "funlee", age: 18, love: "TS"}

if((me as IPerson).name) {
  console.log((me as IPerson).name); // funlee
}

if((me as IMan).love) {
  console.log((me as IMan).love); // TS
}

2.使用類型保護(hù)

為了避免像上例那樣寫一堆類型斷言,我們可以使用類型保護(hù)动漾,如寫一個類型判斷函數(shù):

function isIinterface(obj: IPerson | IMan): obj is IPerson {
  return (obj as IPerson).name !== undefined;
}

這種 param is SomeType 的形式丁屎,就是類型保護(hù),我們可以用它來明確一個聯(lián)合類型變量的具體類型旱眯,在調(diào)用時 TypeScript 就會將變量縮減為該具體類型晨川,如此一來以下調(diào)用就是合法的了:

interface IPerson {
  name: string;
  age: number;
}

interface IMan {
  love: string;
  age: number;
}

let me: IPerson | IMan;
me = {
  name: 'funlee',
  age: 18,
  love: 'TS'
}

function isIPerson(obj: IPerson | IMan): obj is IPerson {
  return (obj as IPerson).name !== undefined;
}
function isIMan(obj: IPerson | IMan): obj is IMan {
  return (obj as IMan).love !== undefined;
}
console.log(me); // {name: "funlee", age: 18, love: "TS"}
if(isIPerson(me)) {
  console.log(me.name); // funlee
}
if(isIMan(me)) {
  console.log(me.love); // TS
}

3.typeof 和 instanceof

當(dāng)我們使用了 typeof 和 instanceof 后证九,TypeScript 就會自動限制類型為某一具體類型,從而我們可以安全地在語句體內(nèi)使用具體類型的方法和屬性共虑。

function show(param: number | string) {
  if (typeof param === 'number') {
    console.log(`${param} is number`)
  } else {
    console.log(`${param} is string`)
  }
}

typeof 用于基本數(shù)據(jù)類型愧怜,instanceof 用于引用類型,對于類妈拌,我們則可以使用 instanceof拥坛,如:

class Person {
  name: string = 'funlee';
  age: number = 18;
}

class Man {
  age: number = 12;
  love: string = 'TS';
}

let me: Person | Man;
me = Math.random() < 0.5 ? new Person() : new Man();

if(me instanceof Person) {
  console.log(me.name);
}
if(me instanceof Man) {
  console.log(me.love);
}

null 的類型

null 和 undefined 可以賦給任何的類型,因?yàn)樗鼈兪撬衅渌愋偷囊粋€有效值尘分,如:

let x1: number = null
let x2: string = null
let x3: boolean = null
let x4: undefined = null
let y1: number = undefined
let y2: string = undefined
let y3: boolean = undefined
let y4: null = undefined

在 TypeScript里猜惋,我們可以使用 --strictNullChecks 標(biāo)記,開啟這個標(biāo)記后培愁,當(dāng)我們聲明一個變量時著摔,就不會自動包含 null 或 undefined,如:

// 開啟`--strictNullChecks`后
// Type 'null' is not assignable to type 'number'.
let x1: number = null

// Type 'null' is not assignable to type 'string'.
let x2: string = null

// Type 'null' is not assignable to type 'boolean'.
let x3: boolean = null

// Type 'null' is not assignable to type 'undefined'.
let x4: undefined = null

// Type 'undefined' is not assignable to type 'number'.
let y1: number = undefined

// Type 'undefined' is not assignable to type 'string'.
let y2: string = undefined

// Type 'undefined' is not assignable to type 'boolean'.
let y3: boolean = undefined

// Type 'undefined' is not assignable to type 'null'.
let y4: null = undefined

但是我們可以手動使用聯(lián)合類型來明確包含定续,如:

et x = 123
x = null // 報錯
let y: number | null = 123
y = null // 允許
y = undefined // 報錯谍咆,`undefined`不能賦值給`number | null`

當(dāng)開啟了 --strictNullChecks,可選參數(shù)/屬性就會被自動地加上 | undefined私股,如:

function foo(x: number, y?: number) {
  return x + (y || 0)
}
foo(1, 2) // 允許
foo(1) // 允許
foo(1, undefined) // 允許
foo(1, null) // 報錯摹察,不允許將null賦值給`number | undefined`類型

類型別名

類型別名可以給現(xiàn)有的類型起個新名字,它和接口很像但又不一樣庇茫,因?yàn)轭愋蛣e名可以作用于原始值、聯(lián)合類型螃成、元組及其他任何需要手寫的了類型旦签,語法如:

type 新名字 = 已有類型

如:type Name = string
別名不會新建一個類型,它只會創(chuàng)建一個新的名字來引用現(xiàn)有類型寸宏。

泛型別名

別名支持泛型宁炫。

type Container<T> = {
  value: T
}

let name: Container<string> = {
  value: 'funlee'
}

但是類型別名不能出現(xiàn)在聲明右側(cè)的任何地方,如:

type Alias = Array<Alias> // 報錯氮凝,別名Alias循環(huán)引用了自身

和接口的區(qū)別

  1. 錯誤信息羔巢、鼠標(biāo)懸停時,不會使用別名罩阵,而是直接顯示為所引用的類型
  2. 別名不能被 extends 和 implements

字符串字面量類型

字符串字面量類型允許我們定義一個別名竿秆,類型為別名的變量只能取固定的幾個值,如:

type Easing = 'ease-in' | 'ease-out' | 'ease-in-out'
let x1: Easing = 'uneasy' // 報錯: Type '"uneasy"' is not assignable to type 'Easing'
let x2: Easing = 'ease-in' // 允許

字符串字面量類型還能用于區(qū)分函數(shù)重載稿壁,如:

function createElement(tagName: 'img'): HTMLImageElement
function createElement(tagName: 'input'): HTMLInputElement
// ... 其他重載函數(shù)
function createElement(tagName: string): Element {
    // ...
}

可辨識聯(lián)合

可以合并字符串字面量類型幽钢、聯(lián)合類型、類型保護(hù)和類型別名來創(chuàng)建可辨識聯(lián)合的高級模式(也稱為標(biāo)簽聯(lián)合或者代數(shù)數(shù)據(jù)類型)傅是,具有3個要素:

  1. 具有普通的字符串字面量屬性——可辨識的特征
  2. 一個類型別名匪燕,用來包含了那些類型的聯(lián)合——聯(lián)合
  3. 此屬性上的類型保護(hù)

創(chuàng)建一個可辨識聯(lián)合類型蕾羊,首先需要聲明將要聯(lián)合的接口,每個接口都要有一個可辨識的特征帽驯,如(kind屬性):

interface Square {
  kind: 'square'
  size: number
}

interface Rectangle {
  kind: 'rectangle'
  width: number
  height: number
}

interface Circle {
  kind: 'circle'
  radius: number
}

現(xiàn)在龟再,各個接口之間還是沒有關(guān)聯(lián)的,所以我們需要使用類型別名來聯(lián)合這幾個接口尼变,如

type Shape = Square | Rectangle | Circle;

現(xiàn)在利凑,使用可辨識聯(lián)合,如:

function area(s: Shape) {
  switch (s.kind) {
    case 'square':
      return s.size * s.size
    case 'rectangle':
      return s.height * s.width
    case 'circle':
      return Math.PI * s.radius ** 2
  }
}

多態(tài)的 this

多態(tài)的 this 類型表示的是某個包含類或接口的子類型享甸,例子如:

class BasicCalculator {
  public constructor(protected value: number = 0) {
  }
  public currentValue(): number {
    return this.value
  }
  public add(operand: number): this {
    this.value += operand
    return this
  }
  public multiply(operand: number): this {
    this.value *= operand
    return this
  }
}

let v = new BasicCalculator(2).multiply(5).add(1).currentValue() // 11

由于使用了 this 類型截碴,當(dāng)子類繼承父類的時候,新的類就可以直接使用之前的方法蛉威,而不需要做任何的改變日丹,如:

class ScientificCalculator extends BasicCalculator {
  public cconstructor(value = 0) {
    super(value)
  }
  public sin() {
    this.value = Math.sin(this.value)
    return this
  }
}
let v = new BasicCalculator(2).multiply(5).sin().add(1).currentValue();

如果沒有 this 類型,那么 ScientificCalculator 就不能夠在繼承 BasicCalculator 的同時還保持接口的連貫性蚯嫌。因?yàn)閙 ultiply 方法會返回 BasicCalculator 類型哲虾,而BasicCalculator 沒有 sin 方法。然而择示,使用 this 類型束凑,multiply 就會返回 this,在這里就是 ScientificCalculator栅盲。

索引類型

索引類型能使編譯器能夠檢查使用了動態(tài)屬性名的代碼汪诉,如:
我們想要完成一個函數(shù),它可以選取對象中的部分元素的值谈秫,那么:

function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
  return names.map(n => o[n])
}

interface Person {
  name: string
  age: number
}

let p: Person = {
  name: 'funlee',
  age: 21
}

let res = pluck(p, ['name']) // 允許

以上代碼解釋如下:

  1. 首先扒寄,使用 keyof 關(guān)鍵字,它是索引類型查詢操作符拟烫,它能夠獲得任何類型 T 上已知的公共屬性名的聯(lián)合该编。如例子中,keyof T 相當(dāng)于 'name' | 'age'
  2. 然后硕淑,K extends keyof T 表明 K 的取值限制于 'name' | 'age'
  3. T[K] 則代表對象里相應(yīng) key 的元素的類型课竣,所以在例子中,p 對象里的 name 屬性置媳,是 string 類型于樟,所以此時 T[K] 相當(dāng)于 Person[name],即相當(dāng)于類型 string拇囊,所以返回的是 string[]隔披,所以 res 的類型為 string[]

所以,根據(jù)以上例子寂拆,舉一反三有:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}
let obj = {
  name: 'funlee',
  age: 21,
  male: true
}
let x1 = getProperty(obj, 'name') // 允許奢米,x1的類型為string
let x2 = getProperty(obj, 'age') // 允許抓韩,x2的類型為number
let x3 = getProperty(obj, 'male') // 允許,x3的類型為boolean
let x4 = getProperty(obj, 'hobby') // 報錯:Argument of type '"hobby"' is not assignable to parameter of type '"name" | "age" | "male"'.

索引類型和字符串索引簽名

keyof 和 T[K] 與字符串索引簽名進(jìn)行交互鬓长,如果有一個帶有字符串索引簽名的類型谒拴,那么 keyof T 為 string,且 T[string] 為索引簽名的類型涉波,如:

interface Demo<T> {
  [key: string]: T
}
let keys: keyof Demo<boolean> // keys的類型為string
let value: Demo<number>['foo'] // value的類型為number

映射類型

我們可能會遇到這么一些需求:

  1. 將一個現(xiàn)有類型的每個屬性都變?yōu)榭蛇x的英上,如:
interface IPerson {
  name: string
  age: number
}

可選版本為:

interface PersonPartial {
  name?: string
  age?: number
}
  1. 或者將每個屬性都變?yōu)橹蛔x的,如:
interface IPersonReadonly {
  readonly name: string
  readonly age: number
}

而現(xiàn)在 typeScript 為我們提供了映射類型啤覆,能夠使得這種轉(zhuǎn)化更加方便苍日,在映射類型里,新類型將以相同的形式去轉(zhuǎn)換舊類型里每個屬性窗声,如以上例子可以改寫為:

type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}
type Partial<T> = {
  [P in keyof T]?: T[P]
}
type PersonReadonly = Readonly<Person>
type PersonPartial = Partial<Person>

我們還可以寫出更多的通用映射類型相恃,如:

// 可為空類型
type Nullable<T> {
  [P in keyof T]: T[P] | null
}

// 包裝一個類型的屬性
type Proxy<T> = {
  get(): T
  set(value: T): void
}
type Proxify<T> = {
  [P in keyof T]: Proxy<T[P]>
}
function proxify(o: T): Proxify<T> {
  // ...
}
let proxyProps = proxify(props)

由映射類型進(jìn)行推斷(拆包)

上面展示了如何包裝一個類型,那么與之相反的就有拆包操作笨觅,示例如:

function unproxify<T>(t: Proxify<T>): T {
  let result = <T>{}
  for (const k in t) {
    result[k] = t[k].get()
  }
  return result
}
let originalProps = unproxify(proxyProps);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拦耐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子见剩,更是在濱河造成了極大的恐慌杀糯,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苍苞,死亡現(xiàn)場離奇詭異固翰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)羹呵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門骂际,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人担巩,你說我怎么就攤上這事方援∶怀矗” “怎么了涛癌?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長送火。 經(jīng)常有香客問我拳话,道長,這世上最難降的妖魔是什么种吸? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任弃衍,我火速辦了婚禮,結(jié)果婚禮上坚俗,老公的妹妹穿的比我還像新娘镜盯。我一直安慰自己岸裙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布速缆。 她就那樣靜靜地躺著降允,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艺糜。 梳的紋絲不亂的頭發(fā)上剧董,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機(jī)與錄音破停,去河邊找鬼翅楼。 笑死,一個胖子當(dāng)著我的面吹牛真慢,可吹牛的內(nèi)容都是我干的毅臊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼晤碘,長吁一口氣:“原來是場噩夢啊……” “哼褂微!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起园爷,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宠蚂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后童社,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體求厕,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年扰楼,在試婚紗的時候發(fā)現(xiàn)自己被綠了呀癣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡弦赖,死狀恐怖项栏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹬竖,我是刑警寧澤沼沈,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站币厕,受9級特大地震影響列另,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜旦装,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一页衙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦店乐、人聲如沸艰躺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽描滔。三九已至,卻和暖如春踪古,著一層夾襖步出監(jiān)牢的瞬間含长,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工伏穆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拘泞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓枕扫,卻偏偏與公主長得像陪腌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子烟瞧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容