TypeScript 高級類型,你瞭解幾個?

前言

不能否認,現在 TypeScript 已成爲一個前端工程師的所須要具有的基本技能。嚴謹的類型檢測,一方面是提升了程序的可維護性健壯性,另外一方面也在潛移默化地提升咱們的編程思惟,即邏輯性javascript

那麼,今天我將會經過結合實際開發場景Vue 3.0 源碼中的部分類型定義來簡單聊聊 TypeScript 中的高級類型。前端

interface

interface 被用於對全部具備結構的數據進行類型檢測。例如,
在實際開發當中,咱們會定義一些對象或數組來描述一些視圖結構。vue

定義對象java

常見的有對錶格的列的定義:ios

const columns = [
  {key: "username", title: "用戶名"},
  {key: "age", title: "年齡"},
  {key: "gender", title: "性別"}
]

而這個時候,咱們就能夠經過定義一個名爲 column 的接口:typescript

interface column {
  key: string,
  title: string
}

// 使用
const columns: column[] = [
  {key: "username", title: "用戶名"},
  {key: "age", title: "年齡"},
  {key: "gender", title: "性別"}
]

定義函數編程

咱們日常開發中使用的 axios,它的調用方式會有不少種,例如 axios.request()axios.get()axios.put()。它本質上是定義了這麼一個接口來約束 axios 具有這些方法,它看起來會是這樣:axios

export interface Axios {
  request(config: AxiosRequestConfig): AxiosPromise
  get(url: string, config?: AxiosRequestConfig): AxiosPromise
  delete(url: string, config?: AxiosRequestConfig): AxiosPromise
  head(url: string, config?: AxiosRequestConfig): AxiosPromise
  options(url: string, config?: AxiosRequestConfig): AxiosPromise
  post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
  put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
  patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
}

繼承

可複用性是咱們日常在寫程序時須要常常思考的地方,例如組件的封裝、工具函數的提取、函數設計模式的使用等等。而對於接口也一樣如此,它能夠經過繼承來複用一些已經定義好的 interfaceclass設計模式

這裏咱們以 Vue 3.0 爲例,它在 compiler 階段,將 AST Element 節點 Node 分爲了多種 Node,例如 ForNodeIFNodeIfBranchNode 等等。而這些特殊的 Node 都是繼承了 Node數組

Nodeinteface 接口定義:

export interface Node {
  type: NodeTypes
  loc: SourceLocation
}

IFNodeinterface 接口定義:

export interface IfNode extends Node {
  type: NodeTypes.IF
  branches: IfBranchNode[]
  codegenNode?: IfConditionalExpression
}

能夠看到 Node 的接口定義是很是純淨的,它描述了 Node 所須要具有最基本的屬性:type 節點類型、loc 節點在 template 中的起始位置信息。而 IFNode 則是在 Node 的基礎上擴展了 branchescodegenNode 屬性,以及重寫了 Nodetype 屬性爲 NodeTypes.IF

這裏簡單介紹一下 IFNode 的兩個屬性:

  • branches 表示它對應的 elseelse if 的節點,它多是一個或多個,因此它是一個數組。
  • codegenNode 則是 Vue 3.0 的 AST Element 的一大特色,它描述了該節點的一些屬性,例如 isBlockpatchFlagdynamicProps 等等,這些會和 runtime 的時候靶向更新和靶向更新密切相關。
近段時間,我也在寫一篇關於 Vue 3.0 如何實現 runtime + compile 優雅地實現靶向更新和靜態提高的文章,應該會在下週末竣工。

小結

對於 interface 的介紹和使用,咱們這裏點到即止。固然,它還有不少高級的使用例如結合泛型、定義函數類型等等。有興趣的同窗能夠自行去了解這方面的實戰。

交叉類型和聯合類型

交叉類型

交叉類型故名思意,有着交叉之效。咱們能夠經過交叉類型來實現多個類型的合併。例如,在 Vue3 中,compile 階段除了會進行 baseParse 以外,還會進行 transform,這樣最後的 AST 纔會進行 generate 生成可執行的代碼。因此,compile 階段它就會對應多個 options,即它也是一個交叉類型:

export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions

而這個 CompilerOptions 類型別名會用於 baseCompiler 階段:

export function baseCompile(
  template: string | RootNode,
  options: CompilerOptions = {}
): CodegenResult {}
而爲何是多個 options 的交叉,是由於 baseCompiler 只是最基礎的 compiler,上面還有更高級的 compiler-domcompiler-ssr 等等。

聯合類型

一樣地,聯合類型其有着合的效果。即有時候,你但願一個變量的類型是 String
或者是 number 的時候,你就可使用聯合類型來約束這個變量。例如:

let numberOrString: number | string
numberOrString = 1
numberOrString = '1'

而且,在實際的開發中使用聯合類型,咱們一般會遇到這樣的提示,例如:

interface student {
  name: string
  age: number
}
interface teacher {
  name: string
  age: number
  class: string
}
let person: student | tearcher = {} as any

person.class = "信息161"
// property 'person' does not exit on type `student`

這個時候,咱們能夠利用類型斷言,告訴 TypeScript 咱們知道 person 是什麼:

(person as teacher).class = "信息161"

小結

對於交叉類型和聯合類型,應該是屬於咱們日常開發中會使用頻繁的部分。而且,從它們的概念上,不難理解,它們本質上就是數學中的並集交集,只不過在基本類型上有着不一樣的表現而已。

類型保護與區分類型

類型保護

在講聯合類型的時候,咱們說了它本質上是交集,這也致使了咱們不能直接使用交集以外的屬性或方法。因此,咱們得經過在不一樣狀況下使用類型斷言來告知 TypeScript 它是什麼,從而使用交集以外的屬性。

可是,頻繁地使用類型斷言無疑下降了代碼的可讀性。而針對這一問題,TypeScript 提供了類型保護的機制來減小類型斷言,它是一個主謂賓句,例如仍然是上面的例子,咱們能夠實現這樣的類型保護:

function isTeacher(person: Tearcher | Student): person is Teacher {
   return (<Tearcher>person).class !== undefined
}

而後經過 isTeacher 這個函數來判斷當前類型,再進行相應地屬性訪問:

if (isTeacher(person)) {
  // 這裏訪問 teacher 獨有的屬性
  person.class = "信息162"
} else {
  person.name = "wjc"
}

typeof 與 instanceof

有了類型保護,這個時候咱們就會遇到這樣的問題:若是個人聯合類型中存在多個類型,那麼豈不是得定義多個相似 isTeacher 這樣的藉助類型保護的函數?幸運的是,在 TypeScript 使用 typeofinstanceof 能夠自動實現類型保護和區分。

typeof

在 TypeScript 中對基礎類型使用 typeof 時,會自動進行類型保護,例如:

function isNumberOrString(param: string | number) {
    if (typeof param === 'string') {
        return param.split('')
    } else {
        return param++
    }
}

instanceof

不一樣於 typeof 只能對基礎類型進行類型保護,instanceof 能夠實現對全部類型的類型保護。它是經過構造函數來實現類型保護。

interface Person {
    name: string
    age: string
  }
  class Teacher implements Person {
    name: string
    age: string
    constructor(name: string, age: string) {
        this.name = name
        this.age = age
    }
    teach() {
      console.log("i am a teacher.")
    }
  }
  class Student implements Person {
    name: string
    age: string
    constructor(name: string, age: string) {
        this.name = name
        this.age = age
    }
    study() {
      console.log("i am a student.")
    }
  }
  const person: Student | Teacher = null as any

 if (person instanceof Teacher) {
     person.teach()
 } else {
     person.study()
 }

小結

對於 typeofinstanceof 其實在 JavaScript 中也是老生常談的知識點,由於傳統的類型檢測咱們會更偏向使用 Object.String.prototype.toString() 來實現。可是,反而在 TypeScript 中,它們二者反而混的如魚得水。

類型別名

類型別名,我想你們腦海中第一時間想到的就是 Webpack 中的 alias 爲路徑配置別名。可是,TypeScript 中的類型別名與它只是形同,可是意不一樣。通俗點講,就是經過它能夠給類型起一個名稱:

type age = number
const getAge = () => age

// 等同於
interface Age {
  getAge():number
}

字面量類型

字面量類型是指咱們能夠經過使用類型別名和聯合類型來實現枚舉類型,例如:

type method = get | GET | post | POST | PUT | put | DELETE | delete

索引類型與映射類型

對於索引類型和映射類型,這裏咱們經過 loadash 中經常使用的一個函數 pluck 來說解:

在 JavaScript 中實現:

function pluck(object, names) {
  return names.map(name => object[name])
}

在 TypeScript 中實現:

function puck<T, K extends keyof T>(object: T, names: K[]): T[K][] {
  return names.map(name => object[name])
}

這裏咱們爲 puck 函數定義了兩個泛型變量 TK,其中 K 是繼承於 T 中全部屬性名的類型,因此形參中 names 被約束爲 T 中屬性的數組,這個過程被稱爲類型索引。而對於 puck 函數的返回值 T[K][] ,則表明返回的值是一個數組,而且數組值被約束爲 T 中屬性值爲 K 的值,這個過程被稱爲索引訪問

理解這兩種概念可能會有點晦澀,可是對於每一者都分開去理解過程會比較有邏輯性。

寫在最後

雖然,TypeScript 已成爲一項前端工程師的必備技能。可是,相信不少小夥伴仍是用的 Javascript 比較多。因此,可能會存在困擾,我該如何提升 TypeScript 編程能力?其實,這個問題很簡單,開源的時代,今天咱們不少問題均可以經過閱讀一些開源的項目源碼來解決。這裏,我推薦你們能夠嘗試着去閱讀 Vue3.0 的源碼,相信經過閱讀,你的 TypeScript 編程能力會有質的飛躍。

寫做不易,若是你以爲有收穫的話,能夠帥氣三連擊!!!

參考資料:

)

往期文章回顧

相關文章
相關標籤/搜索