use

  • The use of extends

    • Allows the extension of another component, either a simple object or a constructor, without the need for extend, primarily to facilitate the extension of single-file components, similar to mixins.

    • The main function is the same as the component, but it is used on a single page rather than globally

    • var CompA = { ... }
      
      // Inherit CompA without calling 'vue. extend'
      var CompB = {
        extends: CompA,
      }
      Copy the code
  • How Mixins are used

    • Receive an array of mixins that can contain instance objects just like normal instance objects, and the options will be incorporated into the final options.

    • Mixin hooks are called in the order they are passed in and are called before the component’s own hooks are called

    • var mixin = {
        created: function () { console.log(1)}}var vm = new Vue({
        created: function () { console.log(2)},mixins: [mixin]
      })
      / / = > 1
      / / = > 2
      Copy the code

Realize the principle of

  • Source code analysis
export function mergeOptions (
  parent: Object,
  child: Object, vm? : Component) :Object {... Format some propertiesif(! child._base) {if (child.extends) { Child. extends can be a constructor and an object
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) { // Child-. mixins[I] is an object that merges the data in mixins in sequence
      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) } }// Merge data
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

Copy the code

The main part of this merge function is that the mergeField function strats[key] is merged in a way that requires looking at option.js to see the specific method of the attribute, as shown below

// strats
const strats = config.optionMergeStrategies
Copy the code
  • elMerge method of

    • 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)
        }
      }
      
      Copy the code
      • This is only done in the development environment, if not passed invmOn the prompt message and passdefaultStratMethods tochildTo replace the data onparentThe data in the
  • dataMerge method of

    • strats.data = function ( // Merge the data of the two objectsparentVal: any, childVal: any, vm? : Component): ?Function {
        if(! vm) {if (childVal && typeofchildVal ! = ='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)
      }
      Copy the code
      • If it’s not passed invmObject: If the child element is not a function, it is prompted in the development environmentThe data attribute should be a function...., and return the parent element; Or merge the parent and child elements
      • If the incomingvmObject: Merges the parent and child elements
    • //mergeDataOrFn merge method
      export function mergeDataOrFn (parentVal: any, childVal: any, vm? : Component): ?Function {
        if(! vm) {if(! childVal) {return parentVal
          }
          if(! parentVal) {return childVal
          }
          return function mergedDataFn () {
            return mergeData(
              typeof childVal === 'function' ? childVal.call(this.this) : childVal,
              typeof parentVal === 'function' ? parentVal.call(this.this) : parentVal
            )
          }
        } else {
          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
            }
          }
        }
      }
      
      Copy the code
      • There is novmObject: returns the parent element if any argument is empty; Or you can merge the parent element’s method to the completed object
      • With the incomingvmObject: returns the result of combining the parent and child elements
    • // mergeData concrete object merge method
      function mergeData (to: Object.from:?Object) :Object {
        if (!from) return to
        let key, toVal, fromVal
        const keys = hasSymbol
          ? Reflect.ownKeys(from)
          : Object.keys(from)
        for (let i = 0; i < keys.length; i++) {
          key = keys[i]
          // in case the object is already observed...
          if (key === '__ob__') continue
          toVal = to[key]
          fromVal = from[key]
          if(! hasOwn(to, key)) { set(to, key, fromVal)// Set from, which does not exist in to, to the object
          } else if( toVal ! == fromVal &&// If two pieces of data are different and both are objects, then bind all elements recursively
            isPlainObject(toVal) &&
            isPlainObject(fromVal)
          ) {
            mergeData(toVal, fromVal)
          }
        }
        return to
      }
      Copy the code
      • Access to thefromAnd loop over the property. iftoObject that does not have the property, will be directly bound to thetoIn the object. iftoThe default is not to bind this property to an existing object. If the value of the property of two objects is different and both objects are objects, the property of this object will be looped together.
  • Life cycle merging approach

    • export const LIFECYCLE_HOOKS = [
        'beforeCreate'.'created'.'beforeMount'.'mounted'.'beforeUpdate'.'updated'.'beforeDestroy'.'destroyed'.'activated'.'deactivated'.'errorCaptured'.'serverPrefetch'
      ]
      
      LIFECYCLE_HOOKS.forEach(hook= > {
        strats[hook] = mergeHook
      })
      Copy the code
    • // mergeHook merges the hook function method
      function mergeHook ( 
        parentVal: ?Array<Function>,
        childVal: ?Function|?Array<Function>
      ): ?Array<Function> {
        const res = childVal
          ? parentVal
            ? parentVal.concat(childVal)
            : Array.isArray(childVal)
              ? childVal
              : [childVal]
          : parentVal
        return res
          ? dedupeHooks(res)
          : res
      }
      Copy the code
      • The main is to put the parent function into the array to return, to achieve the merge
  • component |directive|filterMerge method of

    • export const ASSET_TYPES = [
        'component'.'directive'.'filter'
      ]
      
      ASSET_TYPES.forEach(function (type) {
        strats[type + 's'] = mergeAssets
      })
      Copy the code
    • 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, vm)
          return extend(res, childVal) // Pass the data from the child object to the parent object
        } else {
          return res
        }
      }
      Copy the code
    • export function extend (to: Object, _from: ?Object) :Object {
        for (const key in _from) {
          to[key] = _from[key]
        }
        return to
      }
      Copy the code
      • ifchildValIf there is data in theparentValAll the bindings inchildVal, but for data of the same property,childValWill coverparentValThe data in the
  • watchMerge method of

    • strats.watch = function (
        parentVal: ?Object,
        childVal: ?Object, vm? : Component, key: string): ?Object {... Format validation, etc.const ret = {}
        extend(ret, parentVal)
        for (const key in childVal) {
          let parent = ret[key]
          const child = childVal[key]
          if (parent && !Array.isArray(parent)) {
            parent = [parent]
          }
          ret[key] = parent
            ? parent.concat(child)
            : Array.isArray(child) ? child : [child]
        }
        return ret
      }
      Copy the code
      • childValIs looping toparentVal, if theparentValIf the same attribute exists, parentValandThe corresponding property value in childVal ‘is placed in the array and then used as the property value
  • props|methods|inject|computedMerge method of

    • 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
      }
      Copy the code
      • The merge method is that the latter directly overwrites the former data of the same attribute, and the different data can be directly bound to it
  • provideMerge method of

    • strats.provide = mergeDataOrFn
      Copy the code
      • withdataIn the same way
// config.optionMergeStrategies
export type Config = {
  optionMergeStrategies: { [key: string]: Function };
};
export default ({
  optionMergeStrategies: Object.create(null), 
}: Config)
Copy the code
// defaultStrat directly covers
const defaultStrat = function (parentVal: any, childVal: any) :any {
  return childVal === undefined
    ? parentVal
    : childVal
}
Copy the code

conclusion

Data, provide: The attributes are directly bound to the target object if the target object does not have them. If the target object already exists, it is not bound, but if the property values of the property object are both objects, it is necessary to recursively merge the binding operation on the property value object.

Life cycle: At merge time, replace the property value of the hook function of the target object with an array containing the corresponding function of the target object and the corresponding function of the bound object

Component | directive | filter, props | the methods | inject | computed: at the time of merger, are attributes of the object will be binding on circulation, direct binding target object does not exist, is directly covered

Watch: During the merge, if the target object and the merged object have the same properties, the same properties will be merged, and the different properties will not be bound. The merge is done primarily by replacing the property value with an array containing the object methods of the target object and the corresponding functions of the object to be bound