我是靠谱客的博主 外向乐曲,这篇文章主要介绍Vue 源码分析 —— 规范化选项,现在分享给大家,希望可以做个参考。

规范化选项

new Vue 创建 Vue 实例进行初始化时,最先进行的操作就是合并选项。在合并选项时,对于不同的场景有不同的合并策略

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
if (options && options._isComponent) { // 在初始化子组件时,合并子组件的 options initInternalComponent(vm, options) } else { // 外部调用 new Vue 时合并 options vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) }

可以看到,在创建 Vue 实例是,调用 mergeOptions 来合并选项。而在合并选项过程中,做的第一件事就是规范化选项

复制代码
1
2
3
4
5
6
7
/** * 分别对 props inject directives 进行规范化 */ normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child)

下面来看下上面三个函数的具体实现

规范化 props

按照 Vue 使用文档中说明,在定义 props 时有两种格式,分别是使用数组和对象,先来看下这两种格式的使用

复制代码
1
2
3
4
5
6
7
8
9
10
// 以数组的形式列出 props props: ['name', 'age'] // 以对象形式列出 props,以对象的形式列出 props 可指定每个 prop 的数据类型 props: { name: string, age: age, address: object }

既然 props 存在两种定义格式,那么在使用必然会不方便,因此需要对 props 进行规范化,将其统一成对象格式。现在来看一下 normalizeProps 的实现

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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 对于不同写法规范化的结果

对于数组格式

复制代码
1
2
props: ['name', 'age']

最终转换成下面这样

复制代码
1
2
3
4
5
6
7
8
9
props: { name: { type: null }, age: { type: null } }

对于对象格式

复制代码
1
2
3
4
5
6
7
8
props: { name: string, age: number, address: { type: object } }

最终转换成下面这样

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
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 的规则一样,最终会转换成对象格式

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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,具体的用法可以参考官方-自定义指令文档,而除了可以以对象的形式去定义钩子函数外,官方还提供了一种函数的简写,例如:

复制代码
1
2
3
4
5
6
7
8
{ directives: { 'color-swatch': function(el, binding) { el.style.backgroundColor = binding.value } } }

函数的写法会在 bind, update 钩子中触发相同的行为,并且不关心其他钩子。这个行为就是定义的函数。因此在对 directives 进行规范化时,针对函数的写法会将行为赋予 bind, update 钩子。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
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 } } } } }

函数缓存

在上面代码,有一处代码对性能进行优化,在以后的开发中值得参考学习。

在将参数转换成驼峰格式时,每次调用函数后,都会将计算得倒的进行缓存,下次在调用时,优先使用缓存中的值,而不是重新执行函数,以提高性能。这是典型的以空间换时间的优化,也是偏函数的经典应用

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 进行规范化,三个选项最终都会转换成对象的格式。

最后

以上就是外向乐曲最近收集整理的关于Vue 源码分析 —— 规范化选项的全部内容,更多相关Vue内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(92)

评论列表共有 0 条评论

立即
投稿
返回
顶部