vue options合并流程
- 格式化数据
- 格式化props为小驼峰命名的对象
- 格式化inject为{key : { from: val}}形式对象
- 格式化directives为{ bind: defFn, update: defFn }形式对象
- 依次合并 parent, child.extends(单个继承组件), child.mixins(数组), child.options,取值越靠后越优先
- 合并策略:
- propsData props methods inject computed component directive filter相同key优先child的值
- 如果data和provide是函数,优先使用child的函数执行结果对象,递归遍历该对象合并
如果data和provide是对象,递归遍历对象合并
如果data和provide的某个属性是基础类型,优先使用child的合并 - hook钩子函数和watcher合并成数组,先执行父,再执行子
合并函数入口(vue源码)
src/core/util/options.js 的 mergeOptions函数
/* @flow */
import config from '../config'
import {
warn } from './debug'
import {
nativeWatch } from './env'
import {
set } from '../observer/index'
import {
ASSET_TYPES,
LIFECYCLE_HOOKS
} from 'shared/constants'
import {
extend,
hasOwn,
camelize,
toRawType,
capitalize,
isBuiltInTag,
isPlainObject
} from 'shared/util'
/**
* Option overwriting strategies are functions that handle
* how to merge a parent option value and a child option
* value into the final value.
* 选项重载策略是如何合并父选项的值和子选项的值得到最终的值
*/
const strats = config.optionMergeStrategies
/**
* Options with restrictions
*/
// propsData合并策略:优先使用child的值合并
if (process.env.NODE_ENV !== 'production') {
strats.el = strats.propsData = function (parent, child, vm, key) {
if (!vm) {
warn(
`option "${
key}" can only be used during instance ` +
'creation with the `new` keyword.'
)
}
return defaultStrat(parent, child)
}
}
/**
* Helper that recursively merges two data objects together.
*/
function mergeData (to: Object, from: ?Object): Object {
if (!from) return to
let key, toVal, fromVal
const keys = Object.keys(from)
for (let i = 0; i < keys.length; i++) {
key = keys[i]
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
mergeData(toVal, fromVal)
}
}
return to
}
/**
* Data
*/
export function mergeDataOrFn (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
// in a Vue.extend merge, both should be functions
if (!childVal) {
return parentVal
}
if (!parentVal) {
return childVal
}
// when parentVal & childVal are both present,
// we need to return a function that returns the
// merged result of both functions... no need to
// check if parentVal is a function here because
// it has to be a function to pass previous merges.
return function mergedDataFn () {
return mergeData(
typeof childVal === 'function' ? childVal.call(this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this) : parentVal
)
}
} else if (parentVal || childVal) {
return function mergedInstanceDataFn () {
// instance merge
//data可能是函数也可能是对象,是函数则执行获取结果对象
const instanceData = typeof childVal === 'function'
? childVal.call(vm)
: childVal
const defaultData = typeof parentVal === 'function'
? parentVal.call(vm)
: parentVal
if (instanceData) {
//以子对象为主,将父对象递归合并进去
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}
// option.data合并策略
// 如何data是函数,优先使用child的函数结果对象递归合并
// 如何data是对象,递归遍历对象合并
// 如何data是基础类型,优先使用child的合并
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
if (childVal && typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn.call(this, parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
/**
* Hooks and props are merged as arrays.
* 钩子函数和属性合并成数组,先执行父,再执行子
*/
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
// option.beforeCreate option.created等钩子函数合并策略
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
/**
* Assets
*
* When a vm is present (instance creation), we need to do
* a three-way merge between constructor options, instance
* options and parent options.
*/
function mergeAssets (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): Object {
const res = Object.create(parentVal || null)
if (childVal) {
process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal