规范化选项
在 new Vue
创建 Vue
实例进行初始化时,最先进行的操作就是合并选项。在合并选项时,对于不同的场景有不同的合并策略
if (options && options._isComponent) {
// 在初始化子组件时,合并子组件的 options
initInternalComponent(vm, options)
} else {
// 外部调用 new Vue 时合并 options
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
可以看到,在创建 Vue
实例是,调用 mergeOptions
来合并选项。而在合并选项过程中,做的第一件事就是规范化选项
/**
* 分别对 props inject directives 进行规范化
*/
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
下面来看下上面三个函数的具体实现
规范化 props
按照 Vue
使用文档中说明,在定义 props
时有两种格式,分别是使用数组和对象,先来看下这两种格式的使用
// 以数组的形式列出 props
props: ['name', 'age']
// 以对象形式列出 props,以对象的形式列出 props 可指定每个 prop 的数据类型
props: {
name: string,
age: age,
address: object
}
既然 props
存在两种定义格式,那么在使用必然会不方便,因此需要对 props
进行规范化,将其统一成对象格式。现在来看一下 normalizeProps
的实现
function normalizeProps (options: Object, vm: ?Component) {
const props = options.props
if (!props) return
const res = {}
let i, val, name
if (Array.isArray(props)) {
// 使用数组格式列出 props
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
// 数组中的每一项必须是字符串,将每一项存储到一个对象,使用每一项的指最为对象的 key, 对应的 value 为 { type: null }
name = camelize(val)
res[name] = { type: null }
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.')
}
}
} else if (isPlainObject(props)) {
// 如果使用对象格式列出 props, isPlainObject 判断一个值是否为普通对象
for (const key in props) {
val = props[key]
// 将对象的 key 转换成小驼峰格式
name = camelize(key)
// 使用三元表达式求值
res[name] = isPlainObject(val)
? val
: { type: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
)
}
options.props = res
}
经过上面代码的分析,最终可以总结出 props 对于不同写法规范化的结果
对于数组格式
props: ['name', 'age']
最终转换成下面这样
props: {
name: {
type: null
},
age: {
type: null
}
}
对于对象格式
props: {
name: string,
age: number,
address: {
type: object
}
}
最终转换成下面这样
props: {
name: {
type: string
},
age: {
type: number
},
address: {
type: object
}
}
规范化 inject
inject 在 Vue 不能单独使用,需要与 provide 配合使用。先来看看 Vue 对 provide/inject 的解释
以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效
inject 选项应该是:
- 一个字符串数组,或
- 一个对象,对象的 key 是本地的绑定名,value 是:
- 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
- 一个对象,该对象的:
- from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
- default property 是降级情况下使用的 value
现在来看看 normalizeInject 的实现。 inject 选项有两种写法,数组的方式和对象的方式。和 props 的规则一样,最终会转换成对象格式
function normalizeInject (options: Object, vm: ?Component) {
const inject = options.inject
if (!inject) return
const normalized = options.inject = {}
// 数组格式
if (Array.isArray(inject)) {
for (let i = 0; i < inject.length; i++) {
// from: 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)
normalized[inject[i]] = { from: inject[i] }
}
} else if (isPlainObject(inject)) {
// 对象格式
for (const key in inject) {
const val = inject[key]
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "inject": expected an Array or an Object, ` +
`but got ${toRawType(inject)}.`,
vm
)
}
}
规范化 directive
我们先看看指令选项的用法, Vue
允许我们自定义指令,并且它提供了五个钩子函数 bind
, inserted
, update
, componentUpdated
, unbind
,具体的用法可以参考官方-自定义指令文档,而除了可以以对象的形式去定义钩子函数外,官方还提供了一种函数的简写,例如:
{
directives: {
'color-swatch': function(el, binding) {
el.style.backgroundColor = binding.value
}
}
}
函数的写法会在 bind
, update
钩子中触发相同的行为,并且不关心其他钩子。这个行为就是定义的函数。因此在对 directives
进行规范化时,针对函数的写法会将行为赋予 bind
, update
钩子。
function normalizeDirectives (options: Object) {
const dirs = options.directives
if (dirs) {
for (const key in dirs) {
const def = dirs[key]
// 函数简写的方式最终也会转换成对象形式
if (typeof def === 'function') {
dirs[key] = { bind: def, update: def }
}
}
}
}
函数缓存
在上面代码,有一处代码对性能进行优化,在以后的开发中值得参考学习。
在将参数转换成驼峰格式时,每次调用函数后,都会将计算得倒的进行缓存,下次在调用时,优先使用缓存中的值,而不是重新执行函数,以提高性能。这是典型的以空间换时间的优化,也是偏函数的经典应用
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
// 将 a-b 形式的写法转换成驼峰形式 aB
// 这里调用 cached 函数缓存转换结果
export const camelize = cached((str: string): string => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})
总结
上面的三个方法中,分别对 props
inject
directives
进行规范化,三个选项最终都会转换成对象的格式。