從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
的值是否為object
,isPlainObject
函式的具體實現我們也在後面分析。
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() : '')
})
這裡定義了兩個函式,分別是cached
和camelize
,其中camelize
就是我們上面呼叫的,cached
是在camelize
函式內部呼叫的。
我們先來看看camelize
函式,其實camelize
函式就是執行cached
後返回的一個函式。呼叫cached
時傳入了一個箭頭函式,箭頭函式內部是呼叫了正則的replace
方法,將傳入的str
變數中匹配/-(\w)/g
的變成大寫字母,並且返回replace
後的值。(也就是將-
轉換成駝峰)。
再來看看cached
函式,該函式傳入的變數其實就是camelize
那裡的箭頭函式,首先定義了一個cache
空物件,然後直接返回了cachedFn
函式。我們在外部呼叫camelize(key)
時,其實就是執行了這裡的了cachedFn
函式,str
的值就是傳入的key
的值。很明顯這裡是一個閉包,可以在外部呼叫camelize
函式的時候可以修改或者讀取這裡定義的cache
物件的值。獲取cache
物件中key
為str
變數值的屬性值賦值給hit
變數。如果有hit變數的值,那麼就直接返回hit的值,如果沒有就執行camelize
傳入的箭頭函式,並且將箭頭函式的返回值賦值給catche
物件的str
屬性。如果下次呼叫camelize
函式時傳入了相同的str
,那麼就不會執行箭頭函式,直接返回閉包中的cache
物件的str
屬性的值。這裡是效能優化的一種手段。
例如:第一次呼叫 camelize('name')
後,cache
物件的值就變成了{name: 'name'}。然後在其他地方再次呼叫 camelize('name')
時再次執行cachedFn
函式,此時hit
變數的值為'name'。直接返回hit
變數的值,不會執行傳入的箭頭函式。