從vue源碼看props

前言

平時寫vue的時候知道props有很多種用法,今天我們來看看vue內部是怎麼處理props中那麼多的用法的。

vue提供的props的用法

1. 數組形式

props: ['name', 'value']

2. 對象形式

對象形式內部也提供了三種寫法:

props: {
    // 基礎的類型檢查
    name: String,
    // 多個可能的類型
    value: [String, Number],
    // 對象形式
    id: {
        type: Number,
        required: true
    }
}

props實現的原理

function normalizeProps (options: Object, vm: ?Component) {
  const props = options.props
  if (!props) return
  const res = {}
  let i, val, name
  if (Array.isArray(props)) {
    ...
  } else if (isPlainObject(props)) {
    ...
  } else if (process.env.NODE_ENV !== 'production') {
    ...
  }
  options.props = res
}

normalizeProps函數就是vue實際處理props的地方,從函數名的翻譯我們可以看出該函數的功能就是標準化props的值。該函數主要分成3部分:① 從options對象中獲取props的值並且定義一個res空對象;②幾個if ... else,分別根據props值的不同類型來處理res對象;③ 用處理後的res對象覆蓋原來options對象的props屬性的值。

接下來看看那幾個if ... else的代碼:

if (Array.isArray(props)) {
    i = props.length
    while (i--) {
      val = props[i]
      if (typeof val === 'string') {
        name = camelize(val)
        res[name] = { type: null }
      } else if (process.env.NODE_ENV !== 'production') {
        warn('props must be strings when using array syntax.')
      }
    }
  }

這個代碼實際就是處理props的值爲數組的情況,例如: props: ['name', 'value']。使用while遍歷該數組,如果數組內元素的類型不是字符串並且不是生產環境,那麼就拋錯:‘props的值類型爲數組時,數組裏面的元素的類型就必須是字符串’。如果是字符串的情況下,使用camelize函數處理一下val的值,並且賦值給name變量。這裏的camelize函數的實際作用就是將'-'轉換爲駝峯。camelize函數具體的實現方式在後面分析。然後在res對象上面添加一個爲name變量的屬性,該屬性的值爲空對象 { type: null }

props: ['name', 'value']這種寫法經過上面的處理後就會變成了下面這樣:

props: {
    name: {
        type: null
    },
    value: {
        type: null
    }
}

接下來看看下面這個else if(isPlainObject(props)),這裏的isPlainObject函數實際就是返回props的值是否爲objectisPlainObject函數的具體實現我們也在後面分析。

else if (isPlainObject(props)) {
   for (const key in props) {
     val = props[key]
     name = camelize(key)
     res[name] = isPlainObject(val)
       ? val
       : { type: val }
   }
 }

使用for...in遍歷props對象,和上面一樣使用camelize函數將'-'轉換爲駝峯。這裏有個三目運算:

res[name] = isPlainObject(val) ? val : { type: val }

判斷了一下val如果是object,那麼在res對象上面添加一個爲name變量的屬性,並且將該屬性的值設置爲val。這個其實就是處理下面這種props的寫法:

props: {
   // 對象形式
   id: {
       type: Number,
       required: true
   }
}

如果val不是object,那麼也在res對象上面添加一個爲name變量的屬性,並且將該屬性的值設置爲{ type: val }。這個其實就是處理下面這種props的寫法:

props: {
    // 基礎的類型檢查
    name: String,
    // 多個可能的類型
    value: [String, Number],
}

經過處理後props會變成了下面這樣:

props: {
    name: {
        type: String
    },
    value: {
        type: [String, Number]
    }
}

所以不管我們使用vue提供的props哪種寫法,最終vue都會幫我們轉換成下面這種類型:

props: {
    name: {
        ...,
        type: '類型'
    }
}

接下來看看上面提到的util函數isPlainObject,先把源碼貼出來。

const _toString = Object.prototype.toString

export function isPlainObject (obj: any): boolean {
  return _toString.call(obj) === '[object Object]'
}

其實Object.prototype.toString.call(obj)的值爲obj對象的類型。例如:

Object.prototype.toString.call({a: 1})      // [object Object]
Object.prototype.toString.call(new Date)    // [object Date]
Object.prototype.toString.call([1])         // [object Array]
Object.prototype.toString.call(null)        // [object Null]

接下來看看上面提到的util函數camelize,還是先把源碼貼出來。

export function cached<F: Function> (fn: F): F {
  const cache = Object.create(null)
  return (function cachedFn (str: string) {
    const hit = cache[str]
    return hit || (cache[str] = fn(str))
  }: any)
}

const camelizeRE = /-(\w)/g
export const camelize = cached((str: string): string => {
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})

這裏定義了兩個函數,分別是cachedcamelize,其中camelize就是我們上面調用的,cached是在camelize函數內部調用的。

我們先來看看camelize函數,其實camelize函數就是執行cached後返回的一個函數。調用cached時傳入了一個箭頭函數,箭頭函數內部是調用了正則的replace方法,將傳入的str變量中匹配/-(\w)/g的變成大寫字母,並且返回replace後的值。(也就是將-轉換成駝峯)。

再來看看cached函數,該函數傳入的變量其實就是camelize那裏的箭頭函數,首先定義了一個cache空對象,然後直接返回了cachedFn函數。我們在外部調用camelize(key)時,其實就是執行了這裏的了cachedFn函數,str的值就是傳入的key的值。很明顯這裏是一個閉包,可以在外部調用camelize函數的時候可以修改或者讀取這裏定義的cache對象的值。獲取cache對象中keystr變量值的屬性值賦值給hit變量。如果有hit變量的值,那麼就直接返回hit的值,如果沒有就執行camelize傳入的箭頭函數,並且將箭頭函數的返回值賦值給catche對象的str屬性。如果下次調用camelize函數時傳入了相同的str,那麼就不會執行箭頭函數,直接返回閉包中的cache對象的str屬性的值。這裏是性能優化的一種手段。

例如:第一次調用 camelize('name')後,cache對象的值就變成了{name: 'name'}。然後在其他地方再次調用 camelize('name')時再次執行cachedFn函數,此時hit變量的值爲'name'。直接返回hit變量的值,不會執行傳入的箭頭函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章