vue源碼解讀(五)從vue源碼學習Typescript

歡迎star個人github倉庫,共同窗習~目前vue源碼學習系列已經更新了6篇啦~

https://github.com/yisha0307/...

快速跳轉:javascript

關於Typescript

衆所周知,javascript是一門動態語言,和java這種靜態語言最大的不一樣就是容許改變變量的數據類型。好比在js裏定義一個var a = 123, 這個時候a是一個Number,若是再執行a = [1,2,3], a就變成了一個Array, 這在js裏是徹底能夠的,可是在JAVA這種一開始就會定義a的數據類型的靜態語言中倒是行不通的。動態語言有他的好處,就是寫起來比較靈活,可是缺點也很明顯,因爲對數據類型沒有檢查,經常會致使bug。這個時候,typescript就出現了。vue

TypeScript是一種由微軟開發的自由和開源的編程語言。它是JavaScript的一個超集,並且本質上向這個語言添加了可選的靜態類型和基於類的面向對象編程。

因爲vue的源碼幾乎都是用typeScript寫的,因此在學習vue-src的時候,我也去學習了ts。更重要的是,目前vue3已經出了pre-alpha version, 在vue3裏會全面支持ts,因此學習typeScript就有很大的好處。java

本文會根據源碼中的一些ts寫法對typeScript進行介紹,系統的學習ts能夠參考Typescript入門教程node

TypeScript特性

基礎類型git

Typescript中包含了boolean / number / string / object / Array / 元組 / 枚舉 / any / Undefined / Null / Void / Nevergithub

舉個栗子,在vue-src裏隨處可見的類型檢查:typescript

export function remove (arr: Array<any>, item: any): Array<any> | void {
  if (arr.length) {
    const index = arr.indexOf(item)
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

remove這個函數接受兩個參數arr和item, arr是數組,且數組內的元素能夠是任意類型(any),而item也能夠是任意類型,且返回值能夠是數組也能夠是空值(|表明或的關係,稱之爲」聯合類型「)。編程

再好比vnode的代碼:數組

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  functionalContext: Component | void; // only for functional component root nodes
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions
  ) {
    /*當前節點的標籤名*/
    this.tag = tag
    // 省略了各種屬性的定義
  }

  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next */
  get child (): Component | void {
    return this.componentInstance
  }
}

能夠看到,VNode類在最開始就對this的各種屬性進行了類型聲明.閉包

高級類型
大部分狀況是對object類型作更細的標註, 主要使用interface(接口),除了可用於對類的一部分行爲進行抽象之外,也經常使用於對「對象的形狀(Shape)」進行描述。

interface的簡單例子:

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

let tom: Person = {
    name: 'Tom',
    age: 25
};

在上面的代碼中,定義了一個接口Person, 接着定義了一個變量tom,它的類型是Person。這樣,咱們就約束了tom的形狀必須和接口Person一致。若是tom中少了某個屬性或者多了某個屬性,都是不容許的。

若是有些屬性咱們並不肯定是否是必定有,能夠用?表示可選屬性:

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

let tom: Person = {
    name: 'Tom'
}; // true

若是咱們容許在interface上添加一些未知屬性,可使用:

interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

上面的代碼中,咱們定義了一個string類型的propName, 它的類型能夠是任意屬性any.

看一個vue-src裏的例子:

interface ISet {
  has(key: string | number): boolean;
  add(key: string | number): mixed;
  clear(): void;
}

而在使用這個interface的時候:

class Set implements ISet {
    set: Object;
    constructor () {
      this.set = Object.create(null)
    }
    has (key: string | number) {
      return this.set[key] === true
    }
    add (key: string | number) {
      this.set[key] = true
    }
    clear () {
      this.set = Object.create(null)
    }
}

補充說明一下, implements(實現)是面向對象中的一個重要概念。

通常來說,一個類只能繼承自另外一個類,有時候不一樣類之間能夠有一些共有的特性,這時候就能夠把特性提取成接口(interfaces),用 implements 關鍵字來實現。這個特性大大提升了面向對象的靈活性。
----- 《Typescript》入門教程

在上面的代碼中,ISet這個interface定義了has、add、clear三個方法,而Set類實現了ISet,定義了這三個方法。

泛型

舉個源碼中的例子(由於vue2裏泛型幾乎沒用到,這邊舉的例子是vue3的,來自尤大的vue-next工程):

function last<T>(xs: T[]): T | undefined {
  return xs[xs.length - 1]
}

這個方法的意思就是傳入一個數組(可是數組的每一個值並不清楚是什麼類型),返回該數組的最後一個值。

這樣咱們就有個概念了,所謂泛型(Generics),是指在定義函數、接口或類的時候,不預先指定具體的類型,而在使用的時候再指定類型的一種特性。

上例中,咱們在函數名後添加了<T>,其中T用來指代任意輸入的類型,在後面的輸入(xs: T[])和輸出T或者undefined便可使用了。接着在調用的時候,能夠指定它具體的類型爲 string。類型自動就會推算出來T的類型。

再來一段高級點的代碼,一樣出自vue3:

export function withScopeId(id: string): <T extends Function>(fn: T) => T {
  if (__BUNDLER__) {
    return ((fn: Function) => {
      return function(this: any) {
        pushScopeId(id)
        const res = fn.apply(this, arguments)
        popScopeId()
        return res
      }
    }) as any
  } else {
    return undefined as any
  }
}

這段代碼一樣使用了泛型,可是對泛型進行了約束,只容許傳進來的fn繼承自Function。固然也能夠自定義interface, 好比(例子來自typescript入門教程 - 泛型):

interface Lengthwise {
  length: number
}
function loggingIdentity<T extends Lengthwise> (arg: T): T {
  console.log(arg.length)
  return arg
}

這就保證了泛型T必須符合interface Lengthwise的形狀,也就是會有length屬性。

零零碎碎

數組的不一樣表示方法

  1. let fibonacci: number [] = [1, 1, 2, 3, 5]
  2. let fibonacci: Array<number> = [1, 1, 2, 3, 5]

元組和數組的不一樣

數組合並了相同類型的對象,而元組(Tuple)合併了不一樣類型的對象。也就是數組裏的每個值都必須是同一個數據類型,可是元組無所謂.

好比:

// 數組
let tom: number[] = [1, '2'] // error

// 元組
let tom: [number, string] = [1, '2'] // passed

斷言

斷言有兩種語法,分別是<類型>值或者值 as 類型。在tsx中,只能使用後一種。
好比vue3中:

export function provide<T>(key: InjectionKey<T> | string, value: T) {
  if (!currentInstance) {
    if (__DEV__) {
      warn(`provide() can only be used inside setup().`)
    }
  } else {
    let provides = currentInstance.provides
    const parentProvides =
      currentInstance.parent && currentInstance.parent.provides
    if (parentProvides === provides) {
      provides = currentInstance.provides = Object.create(parentProvides)
    }
    // TS doesn't allow symbol as index type
    provides[key as string] = value
  }
}

斷言適用於確實須要在還不肯定類型的時候就訪問其中一個類型的屬性或方法,好比上例中,key as string就是講key斷言成了string類型。以後key就能夠按照string類型使用string的一些方法。

要注意的是,類型斷言不是類型轉換,斷言成一個聯合類型中不存在的類型是不容許的

相關文章
相關標籤/搜索