鄢栋,微医前端技术部前端工程师。有志成为一名全栈开发工程师甚至架构师,路漫漫,吾求索。 生活中通过健身释放压力,思考问题。
文章篇幅较长, 建议花整块时间阅读分析。 另外由于篇幅过长, 本文分三篇文章产出, 便于大家理解与阅读。
mergeOptions 函数
在上一篇文章分析中,我们分析完了 mergeOptions 函数的第一个参数 resolveConstructorOptions, 我们再回头看看 mergeOptions 这个函数。
作用:把构造函数上的 options 和实例化时传入的 options 进行合并操作并生成一个新的 options
从上述分析可知,mergeOptions 函数是要将实例构造器上的 options, 实例化时传入的 options, 以及当前实例合并成一个 options, 看一下 mergeOptions 函数的定义:
export function mergeOptions (
parent: Object, // 实例构造器上的 options
child: Object, // 实例化时传入的 options
vm?: Component // 当前实例
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
- 1、
checkComponents(child)校验组件的名称是否合法 - 2、如果 child(传入的 options)是 function 类型的话,我们取其 options 属性作为 child
- 3、将 props, inject, directives 转化成对象形式(
normalizeProps(child, vm),normalizeInject(child, vm),normalizeDirectives(child))
第一条和第 2 条我们就不做细致分析了, 有兴趣的可自行查阅。组件名称,我们在使用过程中不要过分特殊就行 O(∩_∩)O~
normalizeProps
我们来看看,vue 是如何将 props 转换的。首先需要了解 props 的用法,我们一般有两种, 一种是数组形式, 一种是对象形式。
// 数组形式
Vue.component('test-component', {
props: ['testData'],
template: '<span>{{ testData }}</span>'
})
// 对象形式
Vue.component('test-component', {
props: {
testData: {
type: String,
default: ''
}
},
template: '<span>{{ testData }}</span>'
})
再看看 normalizeProps 函数的定义
function normalizeProps (options: Object, vm: ?Component) {
const props = options.props
if (!props) return
const res = {}
let i, val, name
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.')
}
}
} else if (isPlainObject(props)) {
for (const key in props) {
val = props[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
}
这段代码,现实声明了 res 这个变量, 后边用来存储处理后的对象, 最后赋值到 options.props 上。
- 如果 props 是数组的形式,就遍历字符串数组,给每一个 key 赋值为{type: null}
也就是数组形式的会被转为:
{
testData: { type: null }
}
- 如果 props 是对象形式, 则同样是先把 key 值驼峰化, 然后判断 key 对应的 value 是不是纯对象, 是就直接赋值给 res 返回;不是就把{type: value}赋值给 res, 所以对象形式的 props 最终会被转为:
{
testData: {
type: String,
default: ''
}
}
normalizeInject
/**
* Normalize all injections into Object-based format
*/
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++) {
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
)
}
}
同样也是分数组和对象形式:
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件数组形式注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
// 子组件对象形式注入 'foo'
const Child = {
inject: {
foo: {
from: 'bar',
default: 'foo'
}
}
}
最终经过 normalizeInject 处理后都会变成:
// array
{
foo: { from: 'foo'}
}
// object
{
foo: {
from: 'bar',
default: 'foo'
}
}
normalizeDirectives
/**
* Normalize raw function directives into object format.
*/
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 }
}
}
}
}
directives 分函数和对象两种写法:
// 对象形式注册
Vue.directive('my-directive', {
bind: function () {},
inserted: function () {},
update: function () {},
componentUpdated: function () {},
unbind: function () {}
})
// 函数注册 (指令函数)
Vue.directive('my-directive', function () {
// 这里将会被 `bind` 和 `update` 调用
})
其中, 源码中会将函数形式的指令处理为 bind 和 update 调用, 也就是函数形式的最终会被处理为:
{
'my-directive': {
bind: function () {},
update: function () {}
}
}
我们再接着看 mergeOptions 下面的代码:
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
当传入的 options 里(child)有 mixin 或者 extends 属性时,再次调用 mergeOptions 方法合并 mixins 和 extends 里的内容到实例的构造函数 options 上(即 parent options)
举起栗子🌰:
const childComponent = Vue.component('child', {
...
mixins: [myMixin],
extends: myComponent
...
})
const myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin')
}
}
}
const myComponent = {
mounted: function () {
this.goodbye()
},
methods: {
goodbye: function () {
console.log('goodbye from mixin')
}
}
}
就会把传入的 mounted, created 钩子处理函数,还有 methods 方法提出来去和 parent options 做合并处理。
到此, 本篇结束。讲了 mergeOptions 函数里的以下内容:
- props 标准化为对象形式
{type: null}或{type: val} - inject 标准化为对象形式
{from: val} - directives 标准化
bind, update 函数 - 传入选项的 extends, mixins 属性的合并
小结
下一篇文章我们会进入到 mergeOptions 函数的核心代码部分: 合并策略。
终于熬完了合并选项的分析(三篇文章), 每天抽出睡觉的时间来啃这块, 非常不容易,促进自己成长, 希望也能给小可爱们带去一些启发, 如果有地方觉得说的不清楚的欢迎在下方评论区提问,讨论~
看到这里的小可爱们, 可顺手点个赞鼓励我继续创作, 创作不易,一起学习, 早日实现财富自由~