再學 TypeScript (2) 高級類型

前言

在以前已經從新的去了解了一下基礎類型的一些知識,如今就繼續向前,進一步瞭解下 TypeScript 中的高級類型以及一些用法typescript

1、字面量類型

  • 字符串字面量類型數組

    有時候能夠直接定義字符串字面量類型簡單方便的指定變量的固定值,實現相似枚舉類型的字符串:安全

    type direction = 'up' | 'down' | 'left' | 'right' | 'static'
    
    function move(type: number): direction {
      switch(type) {
        case 0:
          return 'up'
        case 1:
          return 'down'
        case 2:
          return 'left'
        case 3:
          return 'right'
        default:
          return 'static'
      }
    }
    複製代碼
  • 數字字面量類型dom

    TypeScript 還具備數字字面量類型:函數

    type nums = 0 | 1 | 2
    複製代碼

2、索引類型

當咱們須要獲取某個對象中的一些屬性值時,一般會是這樣:學習

const person = {
  name: 'tom',
  age: 11
}

function getProps(obj: any, keys: string[]) {
  return keys.map(key => obj[key])
}

getProps(person, ['name', 'age']) // ['tom', 11]

// 當指定的 key 不是對象的 key 時,編譯器並無提示錯誤
getProps(person, ['sex'])  // [undefined] 
複製代碼

這時候,可使用索引類型,編譯器就可以檢查使用了動態屬性名的代碼:ui

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

function getProps<T, K extends keyof T>(person: T, keys: K[]): T[K][] {
  return keys.map(key => person[key])
}

const person = {
  name: 'tom',
  age: 11
}

getProps(person, ['name'])  // ['tom']
getProps(person, ['sex'])  // 報錯 不能將類型「"sex"」分配給類型「"name" | "age"」
複製代碼

能夠發現,在定義方法 getProps 時,使用了幾個操做符,理解下這些操做符的做用this

  • 索引類型查詢操做符 keyof Tspa

    對於任何類型 Tkeyof T 的結果爲 T 上已知的公共屬性名的聯合類型。例如:prototype

    let personProps: keyof Person // 'name' | 'age'
    // 也就是等同於下面定義
    let personProps: 'name' | 'age'
    複製代碼
  • 索引訪問操做符 T[K]

    在這裏,類型語法反映了表達式語法。 這意味着 person['name'] 具備類型 Person['name']。然而,就像索引類型查詢同樣,你能夠在普通的上下文裏使用 T[K] ,這正是它的強大所在。 你只要確保類型變量 K extends keyof T 就能夠了。 例以下面 getProperty 函數的例子:

    function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
      return o[name]
    }
    
    // 當你返回 T[K]的結果,編譯器會實例化鍵的真實類型
    // 所以 getProperty 的返回值類型會隨着你須要的屬性改變
    
    const name = getProperty(person, 'name')  // string 類型
    const age = getProperty(person, 'age')  // number 類型
    複製代碼
  • 繼承 extends

    K extends keyof T 是泛型約束,泛型變量經過繼承某些類型獲取某些屬性,表示泛型 K 繼承 keyof T 的屬性名

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

keyofT[K] 與字符串索引簽名進行交互。 若是你有一個帶有字符串索引簽名的類型,那麼 keyof T 會是 string 。 而且 T[string] 爲索引簽名的類型:

interface Obj<T> {
  [key: string]: T
}

let keys: keyof Obj<number> // string 類型
let value: Obj<number>['foo'] // number 類型

// 能夠利用索引類型定義屬性值爲某種類型的對象
const datesObj: Obj<number> = {  
  yesterday: 8,
  today: 9,
  tomorrow: 10
}

// 一樣 數組也是適用的
interface Arr<T> {
  [index: number]: T
}
const datesArr: Arr<number> = [8, 9, 10]
複製代碼

3、映射類型

映射類型是 TypeScript 提供了從舊類型中建立新類型的一種方式,在映射類型裏,新類型以相同的形式去轉換舊類型裏每一個屬性。

當須要某個類型可是對屬性要求不同時,就可使用映射類型來指定:

interface Person {
  name: string
  age: number
}

type partial<T> = {
  [P in keyof T]?: T[P]  // 建立一個映射類型 依賴 Person 類型 但屬性是可選的
}

const Jack: partial<Person> = {
  name: 'jack'
}
複製代碼

觀察上面示例代碼,在定義類型的 key 時,用到了前面的索引類型查詢操做符(keyof,用來遍歷每一個屬性索引,經過 in 操做符指向 P ,能夠理解成 for ... in

type Keys = 'opt1' | 'opt2'
type Flags = {
  [K in Keys]: boolean
}
// 類型變量 K,它會依次綁定到每一個屬性
// 字符串字面量聯合的 Keys,它包含了要迭代的屬性名的集合
// 屬性的結果類型 Flags
// 這個映射類型等同於:
type Flags = {
  opt1: boolean
  opt2: boolean
}
複製代碼

4、交叉類型與聯合類型

  • 交叉類型:

    交叉類型是將多個類型合併爲一個類型。 這讓咱們能夠把現有的多種類型疊加到一塊兒成爲一種類型,它包含了所需的全部類型的特性。

  • 聯合類型:

    聯合類型表示一個值能夠是幾種類型之一。

從這兩種類型的定義中,能夠知道,交叉類型( A & B)同時具備類型 A 和類型 B 二者的全部屬性,能夠訪問全部子類型的成員;聯合類型(X | Y)則是表示值是其中一種類型,只能訪問子類型的共有的成員。

interface A {
  name: string
  age: number
  getName: () => string
}

interface B {
  name: string
  age: number
  getAge: () => number
}

function getIntersection (): A & B { }
function getUnion(): A | B { }

// 交叉類型 具備全部子類型的屬性
const x = getVariable()
x.getName()
x.getAge()

// 聯合類型 表示值的類型是幾種類型之一
// 能肯定的就是共有的屬性,因此能夠直接訪問
// 不能訪問某個子類型單獨具備的屬性
const y = getUnion()
y.getName()  // 不存在該屬性
y.getAge()  // 不存在該屬性
y.name  // 能夠訪問
複製代碼

在某些狀況下,當咱們肯定一個聯合類型的變量是其中哪一種類型或者須要在某種類型時會執行類型下的一些方法,若是不是公共成員會報錯,這時候就須要用上類型斷言了:

(<B>y).getName()
複製代碼

在實際開發中,交叉類型用的比較少,更多狀況下,用的是聯合類型。

5、類型註解、類型推斷和類型斷言以及類型保護

類型註解

所謂類型註解,就是人爲爲一個變量指定類型,例如:

const count: number = 0
複製代碼

在定義變量的時候能夠手動給變量添加類型,不過其實是不須要的,由於 TypeScript 會根據變量值自動推斷出變量的類型,若是沒法推斷就默認爲any類型,這就是類型推斷。所以,在大部分狀況下,咱們不須要去寫類型註解;可是在某些沒法推斷的狀況下就須要類型註解了,例如函數的參數:

// 參數的類型沒法推斷 默認其類型爲 any
// 因此 x 、y 和 sum 都被推斷成了 any 類型
function add(x, y) {
  return x + y
}
const sum = add(1, 1)

// 這時就用上類型註解了
// 指定參數的類型是 number ts 會推斷出 sum 也是 number 類型
function add(x: number, y: number) {
  return x + y
}
const sum = add(1, 1)
複製代碼

還有一種狀況就是變量聲明時未賦值,也沒法推斷:

let z;  // any 類型
z = 9
複製代碼

類型斷言

當咱們肯定某個變量的類型,比定義時推斷或註解的更準確,能夠經過類型斷言來手動指定變量的類型。它只是編譯階段起做用,檢查代碼,並不會實際的進行類型的轉換。

const str: any = 'something'
const len1 = <string>str.length  // 尖括號語法
const len2 = (str as string).length  // as 語法
複製代碼

在一些沒法肯定其具體類型的狀況下,在函數實現中,一般須要區分出具體類型:

interface Bird {
  fly()
  layEggs()
}
interface Fish {
  swim()
  layEggs()
}
function getSmallPet(): Fish | Bird {
    // ...
}
let pet = getSmallPet()

pet.fly()  // 錯誤 類型「Bird | Fish」上不存在屬性「fly」
pet.swin()  // 錯誤 類型「Bird | Fish」上不存在屬性「swin」

// 使用類型斷言
if ((<Fish>pet).swim) {
  (<Fish>pet).swim()
} else {
  (<Bird>pet).fly()
}
複製代碼

類型保護

上面例子中,爲了經過類型檢查,須要屢次使用類型斷言,明顯這不是一種優雅的辦法,最好的辦法就是一旦檢查過類型就記錄下來,不須要屢次的指定類型。這種就是類型保護。類型保護就是一些表達式,它們會在運行時檢查以確保在某個做用域裏的類型。

  • 自定義類型保護

    要定義一個類型保護,咱們只要簡單地定義一個函數,它的返回值是一個 類型謂詞

    function isFish(pet: Fish | Bird): pet is Fish {
      return (<Fish>pet).swim !== undefined
    }
    複製代碼

    pet is Fish 就是類型謂詞,謂詞爲 parameterName is Type 這種形式, parameterName 必須是來自於當前函數的一個參數名。每當使用一些變量調用 isFish 時,TypeScript 會將變量縮減爲那個具體的類型,只要這個類型與變量的原始類型是兼容的。

    if (isFish(pet)) {
      pet.swim()
    } else {
      pet.fly()
    }
    複製代碼
  • typeof 類型保護

    在 JavaScript 中,一般使用 typeof 來肯定基本類型, TypeScript 能識別 typeof 做爲類型保護,能夠直接在代碼裏檢查類型了。

    function getAttributeValue(attribute: number | string) {
      if(typeof attribute === 'string') {
        // ...
      } else {
        // ...
      }
    }
    複製代碼

    typeof 類型保護只有兩種形式能被識別: typeof v === "typename"typeof v !== "typename"

    "typename"必須是 "number""string""boolean""symbol"

  • instanceof 類型保護

    instanceof 運算符用於檢測構造函數的 prototype 屬性是否出如今某個實例對象的原型鏈上。

    相似於typeof 檢測基本類型,instanceof 用來檢測實例與類的所屬關係,也是一種類型保護,是經過構造函數來細化類型的一種方式。

    class NumberValue {
      constructor(private value: number) { }
      public getValue() {
        return (this.value * 100).toString()
      }
    }
    class StringValue {
      constructor(private value: string) { }
      public getValue() {
        return (Number(this.value) * 100).toString()
      }
    }
    function getValue() {
      return Math.random() < 0.5 ? new NumberValue(1) : new StringValue('2')
    }
    
    const value = getValue()
    if(value instanceof NumberValue) {
      // ...
    } else {
      // ...
    }
    複製代碼
  • in 類型保護

    若是指定的屬性在指定的對象或其原型鏈中,則 in 運算符返回true

    in 操做符能夠安全的檢查一個對象上是否存在一個屬性,它一般也被作爲類型保護使用:

    interface X {
    	x: number
    }
    interface Y {
    	y: number
    }
    
    function do(arg: X | Y) {
      if('x' in X) {
        // ...
      } else {
        // ...
      }
    }
    複製代碼
  • 字面量類型保護

    在聯合類型裏使用字面量類型時,能夠直接經過判斷聯合類型中的公共屬性:

    type X {
    	name: 'x'
    	do: number
    }
    type Y {
    	name: 'y'
    	do: number
    }
    
    function do(arg: X | Y) {
      if(arg.name === 'x') {
        // ...
      } else {
        // ...
      }
    }
    複製代碼

6、類型別名 type

TypeScript 使用 type 關鍵字聲明類型別名,類型別名並不會新建一個類型,只是建立一個名字來引用一些類型。

type Name = string
type Container<T> = { value: T }  // 類型別名也能夠是泛型
type Tree<T> = {  // 可使用類型別名來在屬性裏引用本身
    value: T
    left: Tree<T>
    right: Tree<T>
}
複製代碼

typeinterface

  • 這二者都是類型約束的主要形式,能夠限定對象的類型,檢查類型。通常在一些簡單的類型定義,能夠同樣使用

  • type 並無 interface 那麼多的使用場景, interface 能夠被被 extendsimplements,可是 type 不行。

type TPersion = {
  name: string
  age: number
}

interface IPersion {
  name: string
  age: number
}

const jack: TPersion = {
  name: 'jack',
  age: 20
}

const tom: IPersion = {
  name: 'tom',
  age: 21
}	

interface IMan extends IPersion {
  log: () => void
}

class Man implements IMan {
  name: string
  age: number
  log() {
    console.log(this.name)
  }
}
複製代碼

至此,已經簡單的梳理了一些高級類型的知識點,這篇其實早就快寫完了,前段時間由於工做太忙,而後又本身犯懶鴿了很久,也是一直沒搞學習,一眨眼就一個月多過去了。如今空閒點了,再從新搞起。

相關文章
相關標籤/搜索