banner
 Sayyiku

Sayyiku

Chaos is a ladder
telegram
twitter

TypeScript 關鍵字

類型約束 extends#

語法:T extends K,這裡的 extends 不是類、介面的繼承,而是對於類型的判斷和約束,意思是判斷 T 能否賦值給 K

判斷 T 是否可以賦值給 U,可以的話返回 T,否則返回 never

type Exclude<T, U> = T extends U ? T : never

類型映射 in#

遍歷指定介面的 key 或者是遍歷聯合類型:

interface Person {
  name: string
  age: number
  gender: number
}

// 將 T 的所有屬性轉換為只讀類型
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

// type ReadonlyPerson = {
//   readonly name: string
//   readonly age: number
//   readonly gender: number
// }
type ReadonlyPerson = Readonly<Person>

類型謂詞 is#

TypeScript 裡有類型保護機制。要定義一個類型保護,我們只要簡單地定義一個函數,它的返回值是一個類型謂詞

function isString(test: any): test is string {
  return typeof test === 'string'
}

上述寫法與寫一個返回值為 boolean 值函數的區別在哪裡呢?

function isString(test: any): boolean {
  return typeof test === 'string'
}

當使用 is 類型保護:

function isString(test: any): test is string {
  return typeof test === 'string'
}

function example(foo: any) {
  if (isString(foo)) {
    console.log('it is a string' + foo)
    console.log(foo.length) // string function
    // 如下代碼編譯時會出錯,運行時也會出錯,因為 foo 是 string 不存在 toExponential 方法
    console.log(foo.toExponential(2))
  }
  // 編譯不會出錯,但是運行時出錯
  console.log(foo.toExponential(2))
}
example('hello world')

當返回值為 boolean:

function isString(test: any): boolean {
  return typeof test === 'string'
}

function example(foo: any) {
  if (isString(foo)) {
    console.log('it is a string' + foo)
    console.log(foo.length) // string function
    // foo 為 any,編譯正常。但是運行時會出錯,因為 foo 是 string 不存在 toExponential 方法
    console.log(foo.toExponential(2))
  }
}
example('hello world')

總結:

  • 在使用類型保護時,TS 會進一步縮小變量的類型。例子中,將類型從 any 縮小至了 string;
  • 類型保護的作用域僅僅在 if 後的塊級作用域中生效。

實戰:

function isAxiosError(error: any): error is AxiosError {
  return error.isAxiosError
}

if (isAxiosError(err)) {
  code = `Axios-${err.code}`
}

待推斷類型 infer#

可以用 infer P 來標記一個泛型,表示這個泛型是一個待推斷的類型,並且可以直接使用。

獲取函數參數類型:

type ParamType<T> = T extends (param: infer P) => any ? P : T

type FunctionType = (value: number) => boolean

type Param = ParamType<FunctionType> // type Param = number

type OtherParam = ParamType<symbol> // type Param = symbol

判斷 T 是否能賦值給 (param: infer P) => any,並且將參數推斷為泛型 P,如果可以賦值,則返回參數類型 P,否則返回傳入的類型。

獲取函數返回類型:

type ReturnValueType<T> = T extends (param: any) => infer U ? U : T

type FunctionType = (value: number) => boolean

type Return = ReturnValueType<FunctionType> // type Return = boolean

type OtherReturn = ReturnValueType<number> // type OtherReturn = number

判斷 T 是否能賦值給 (param: any) => infer U,並且將返回值類型推斷為泛型 U,如果可以賦值,則返回返回值類型 P,否則返回傳入的類型。

原始類型保護 typeof#

語法:typeof v === 'typename'typeof v !== 'typename',用來判斷數據的類型是否是某個原始類型(numberstringbooleansymbol)並進行類型保護。

typename 必須是 numberstringbooleansymbol。 但是 TypeScript 並不會阻止你與其它字符串比較,語言不會把那些表達式識別為類型保護。

示例:print 函數會根據參數類型打印不同的結果,那如何判斷參數是 string 還是 number 呢?

function print(value: number | string) {
  // 如果是 string 類型
  // console.log(value.split('').join(', '))
  // 如果是 number 類型
  // console.log(value.toFixed(2))
}

兩種常用的判斷方式:

  1. 根據是否包含 split 屬性判斷是 string 類型,是否包含 toFixed 方法判斷是 number 類型。弊端:不論是判斷還是調用都要進行類型轉換。
  2. 使用類型謂詞 is。弊端:每次都要去寫一個工具函數,太麻煩。

故這裡可以使用 typeof

function print(value: number | string) {
  if (typeof value === 'string') {
    console.log(value.split('').join(', '))
  } else {
    console.log(value.toFixed(2))
  }
}

使用 typeof 進行類型判斷後,TypeScript 會將變量縮減為那個具體的類型,只要這個類型與變量的原始類型是兼容的。

類型保護 instanceof#

typeof 類似,不過作用方式不同,instanceof 類型保護是通過構造函數來細化類型的一種方式。

instanceof 的右側要求是一個構造函數,TypeScript 將細化為:

  • 此構造函數的 prototype 屬性的類型,如果它的類型不為 any 的話
  • 構造簽名所返回的類型的聯合
class Bird {
  fly() {
    console.log('Bird flying')
  }
  layEggs() {
    console.log('Bird layEggs')
  }
}

class Fish {
  swim() {
    console.log('Fish swimming')
  }
  layEggs() {
    console.log('Fish layEggs')
  }
}

const bird = new Bird()
const fish = new Fish()

function start(pet: Bird | Fish) {
  // 調用 layEggs 沒問題,因為 Bird 或者 Fish 都有 layEggs 方法
  pet.layEggs()

  if (pet instanceof Bird) {
    pet.fly()
  } else {
    pet.swim()
  }

  // 等同於下面
  // if ((pet as Bird).fly) {
  //   (pet as Bird).fly();
  // } else if ((pet as Fish).swim) {
  //   (pet as Fish).swim();
  // }
}

索引類型查詢操作符 keyof#

語法:keyof T,對於任何類型 Tkeyof T 的結果為 T 上已知的公共屬性名的聯合。

keyof 與 Object.keys 略有相似,只不過 keyof 取 interface 的鍵。

interface Point {
  x: number
  y: number
}

// type keys = "x" | "y"
type keys = keyof Point

假設有一個 object 如下所示,我們需要使用 typescript 實現一個 get 函數來獲取它的屬性值:

function get(o: object, name: string) {
  return o[name]
}

我們剛開始可能會這麼寫,不過它有很多缺點:

  1. 無法確認返回類型:這將損失 ts 最大的類型校驗功能;
  2. 無法對 key 做約束:可能會犯拼寫錯誤的問題。

這時可以使用 keyof 來加強 get 函數的類型功能:

function get<T extends object, K extends keyof T>(o: T, name: K): T[K] {
  return o[name]
}

需要注意,keyof 只能返回類型上已知的公共屬性名

class Animal {
  type: string
  weight: number
  private speed: number
}

type AnimalProps = keyof Animal // 'type' | 'weight'

當需要獲取對象的某個屬性值,但是不確定是哪些屬性,這個時候可以使用 extends 配合 typeof 對屬性名進行限制,限制傳入的參數只能是對象的屬性名:

const person = {
  name: 'Jack',
  age: 20,
}

function getPersonValue<T extends keyof typeof person>(fieldName: keyof typeof person) {
  return person[fieldName]
}

const nameValue = getPersonValue('name')
const ageValue = getPersonValue('age')
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。