Vue2.x选项合并策略一(源码解读)

    技术2022-07-15  64

    什么是选项合并策略 在Vue中,使用mixins的时候发现:

    混入的methods里的方法如果与组件里的方法同名时,会被组件的方法覆盖;但生命周期函数如果重名,混入的生命周期函数与组件本身的都会被执行,执行顺序为先执行混入后执行自身。

    这主要与Vue中的选项合并策略有关,不同的选项有不同的合并策略。

    例如: data、props、methods都是同属性直接覆盖合并; created、mounted等生命周期函数都是直接合并,同名的函数存放在一个数组中,依次进行调用。

    Vue提供一个api:

    Vue.config.optionMergeStrategies

    可以通过这个api实现自定义的选项合并策略。

    在代码中打印:

    console.log(Vue.config.optionMergeStrategies)

    可以看出,不同的选项都有各自的合并策略

    tips:以下父组件选项可理解为mixins混入的数据;子组件选项为当前自身组件选项。

    默认合并策略 defaultStrat

    解读源码: 传入两个参数parentVal、childVal分别对应父组件和子组件的选项; 采用的合并的策略为子组件选项不存在时返回父组件选项,否则返回子组件。

    /** * Default strategy. */ const defaultStrat = function (parentVal: any, childVal: any): any { return childVal === undefined ? parentVal : childVal }

    el、propsData 以此可以看出:el和propsData的合并策略就是属于默认合并策略。

    /** * Options with restrictions */ 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) } }

    props、methods、computed、inject

    源码解读: 若父组件选项不存在,直接返回子组件选项; 否则,创建一个空对象ret,父组件选项覆盖ret属性;若子组件选项存在,则覆盖父组件选项中同名的属性。 即:父子组件选项中存在同名属性,则子组件选项属性覆盖父组件选项属性。

    /** * Other object hashes. */ strats.props = strats.methods = strats.inject = strats.computed = function (parentVal: ?Object, childVal: ?Object, vm?: Component, key: string): ?Object { if (childVal && process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm) } if (!parentVal) return childVal const ret = Object.create(null) extend(ret, parentVal) if (childVal) extend(ret, childVal) return ret } /** 遍历合并数据,子组件选项同名属性覆盖父组件 * Mix properties into target object. */ export function extend (to: Object, _from: ?Object): Object { for (const key in _from) { to[key] = _from[key] } return to }

    data 源码解读: 如果传入参数‘vm’,则表示组件为根实例。 若子组件选项‘data’存在但是不为function类型,直接返回父组件选项; 否则进行合并策略。

    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(parentVal, childVal) } return mergeDataOrFn(parentVal, childVal, vm) }

    源码解读: 若子组件data不存在,返回父组件data;若父组件data不存在,返回子组件data。 进行合并策略。

    /** * 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 } // 若组件选项类型为function,需要使用call()指向当前this return function mergedDataFn () { return mergeData( typeof childVal === 'function' ? childVal.call(this, this) : childVal, typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal ) } } else { // parent为根实例 return function mergedInstanceDataFn () { // instance merge const instanceData = typeof childVal === 'function' ? childVal.call(vm, vm) : childVal const defaultData = typeof parentVal === 'function' ? parentVal.call(vm, vm) : parentVal // 若存在实例选项数据,实现数据合并 if (instanceData) { return mergeData(instanceData, defaultData) } else { return defaultData } } } }

    源码解读: 递归合并两个数据对象

    /** * Helper that recursively merges two data objects together. */ function mergeData (to: Object, from: ?Object): Object { // 若不存在父组件数据,无需合并,直接返回子组件数据 if (!from) return to let key, toVal, fromVal // 获取父组件数据属性对应key值,返回一个key集合数组 const keys = hasSymbol ? Reflect.ownKeys(from) : Object.keys(from) // 遍历父组件数据keys for (let i = 0; i < keys.length; i++) { key = keys[i] // in case the object is already observed... if (key === '__ob__') continue // 获取子组件属性key的value值 toVal = to[key] // 获取父组件属性key的value值 fromVal = from[key] // 如果子组件数据对象中不存在该key,如果存在则不做改动 if (!hasOwn(to, key)) { // 将父组件key对应的value值赋值给子组件数据属性key set(to, key, fromVal) // 如果父子组件当前key对应的value不相等,且为Object类型时,进行递归 } else if (toVal !== fromVal && isPlainObject(toVal) && isPlainObject(fromVal)) { mergeData(toVal, fromVal) } } return to } // 判断浏览器是否支持某一方法 [native code] 如ES6新特性‘Symbol’、‘Reflect’等 /* istanbul ignore next */ export function isNative (Ctor: any): boolean { return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) } export const hasSymbol = typeof Symbol !== 'undefined' && isNative(Symbol) && typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys) /** * Get the raw type string of a value, e.g., [object Object]. */ const _toString = Object.prototype.toString /** * Strict object type check. Only returns true * for plain JavaScript objects. */ export function isPlainObject (obj: any): boolean { return _toString.call(obj) === '[object Object]' }

    watch 源码解读: 实现合并策略,不进行数据覆盖;合并在一个数组,先执行父组件数据,再执行子组件数据。

    /** * Watchers. * Watchers hashes should not overwrite one * another, so we merge them as arrays. */ strats.watch = function (parentVal: ?Object, childVal: ?Object, vm?: Component, key: string): ? Object { // work around Firefox's Object.prototype.watch... // 此处nativeWatch相当于undefined if (parentVal === nativeWatch) parentVal = undefined if (childVal === nativeWatch) childVal = undefined /* istanbul ignore if */ // 如果子组件数据不存在,返回父组件数据对象 || null if (!childVal) return Object.create(parentVal || null) if (process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm) } // 如果父组件不存在,直接返回子组件数据 if (!parentVal) return childVal const ret = {} // 合并对象 extend(ret, parentVal) // 遍历子组件数据对象 for (const key in childVal) { // 获取父组件数据对应key的value值 let parent = ret[key] // 获取子组件数据对应key的value值 const child = childVal[key] // 如果父组件value值存在,但不为数组类型;将其设置为数组 if (parent && !Array.isArray(parent)) { parent = [parent] } // 如果父组件数据value存在,则拼接子组件数据value,合并成一个数组; // 如果父组件数据value不存在,则判断子组件数据value是否为数组类型,若不是则将其设置为数组 ret[key] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child] } return ret } // Firefox has a "watch" function on Object.prototype... // nativeWatch = undefined export const nativeWatch = ({}).watch function assertObjectType (name: string, value: any, vm: ?Component) { if (!isPlainObject(value)) { warn( `Invalid value for option "${name}": expected an Object, ` + `but got ${toRawType(value)}.`, vm ) } }

    其他生命周期钩子 源码解读: 如果父子组件数据只有一个存在,则返回该数据,并以数组的形式; 如果父子组件数据同时存在,则合并两个数据,以数组的形式;父组件数据在前,子组件数据在后; 依次执行父子组件数据。

    // 生命周期函数 var LIFECYCLE_HOOKS = [ 'beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'beforeDestroy', 'destroyed', 'activated', 'deactivated', 'errorCaptured' ]; /** * 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 } LIFECYCLE_HOOKS.forEach(function (hook) { strats[hook] = mergeHook; });

    Vue2.x选项合并策略二(自定义选项合并策略和生命周期函数)

    Processed: 0.015, SQL: 9