Code前端首页关于Code前端联系我们

延长mixins的实施

terry 2年前 (2023-09-08) 阅读数 165 #Vue

使用方法

  • 如何使用

    扩展

    • 允许你扩展另一个组件,可以是一个简单的对象或构造函数,而不需要使用扩展,这主要是为了方便扩展单文件组件,这与mixins类似。

    • main 函数适用于同一个组件,但是是在一页上使用,而不是全局

    • var CompA = { ... }
      
      // 在没有调用 `Vue.extend` 时候继承 CompA
      var CompB = {
        extends: CompA,
      }
       
  • 如何使用Mixins

    • 接收一组混合对象,这些混合对象可以包含徐翔实例作为普通实例对象,这些选项将组合成最终选项。

    • Mixin的hook按照传递的顺序依次调用,在组件自己的hook被调用之前调用

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

实现原理

  • 源码分析
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
   ...格式化一些属性
  if (!child._base) {
    if (child.extends) { // 如果存在extends方法将组件进行合并 child.extends 可以是个构造函数和对象
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) { // child.mixins[i]  是个对象 ,将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
}

 

连接函数中最重要的部分是mergeField 函数中的strats[key]。您必须检查option.js特定的加入方法才能查看特定属性的特定方法。以下信息

// strats
const strats = config.optionMergeStrategies
 
  • el连接方法

    • 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)
        }
      }
      
       
      • 仅适用于开发环境。如果vm没有通过,则会显示一条信息,并使用defaultStrat方法直接将child上的数据替换为
      • 中的数据
  • 如何组合

    data

    • 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)
      }
       
      • 如果没有传入对象:如果子元素不是函数,则会在开发环境中调用,并返回父元素;或者合并父元素和子元素
      • 如果传递对象vm:合并父子元素
    • //mergeDataOrFn 合并方法
      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
            }
          }
        }
      }
      
       
      • vm对象:任何父子元素参数为空,将返回另一个元素;或者父子元素方法完成后会指向对象,对象会被合并并返回
      • 有传入对象vm时:父子函数执行后的结果合并返回给
    • // mergeData 具体的对象合并方法
      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) // 将to中不存在的from设置到to对象中
          } else if (
            toVal !== fromVal && // 如果两个数据不相同且都是对象的话,然后将元素中的所有进行递归的绑定
            isPlainObject(toVal) &&
            isPlainObject(fromVal)
          ) {
            mergeData(toVal, fromVal)
          }
        }
        return to
      }
       
      • 获取from中对象的所有属性,并对属性进行循环操作。如果对象中不存在该属性,则直接绑定到对象to;如果to对象中已存在该属性,则默认不绑定该属性。如果两个对象的属性值不同且所有对象都相同时,则会循环合并对象属性。
  • 生命周期耦合方法

    • export const LIFECYCLE_HOOKS = [
        'beforeCreate',
        'created',
        'beforeMount',
        'mounted',
        'beforeUpdate',
        'updated',
        'beforeDestroy',
        'destroyed',
        'activated',
        'deactivated',
        'errorCaptured',
        'serverPrefetch'
      ]
      
      LIFECYCLE_HOOKS.forEach(hook => {
        strats[hook] = mergeHook
      })
       
      • 基本上是将父子函数插入数组并返回它以进行连接
  • export function mergeOptions (
      parent: Object,
      child: Object,
      vm?: Component
    ): Object {
       ...格式化一些属性
      if (!child._base) {
        if (child.extends) { // 如果存在extends方法将组件进行合并 child.extends 可以是个构造函数和对象
          parent = mergeOptions(parent, child.extends, vm)
        }
        if (child.mixins) { // child.mixins[i]  是个对象 ,将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
    }
    
     
    |directive|filter如何组合
    • export const ASSET_TYPES = [
        'component',
        'directive',
        'filter'
      ]
      
      ASSET_TYPES.forEach(function (type) {
        strats[type + 's'] = mergeAssets
      })
       
    • 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) // 将子对象中的数据传入到父对象中
        } else {
          return res
        }
      }
       
    • export function extend (to: Object, _from: ?Object): Object {
        for (const key in _from) {
          to[key] = _from[key]
        }
        return to
      }
       
      • 如果数据在childVal中,则中的所有参数都会绑定在parentVal中,但对于相同属性的数据,childVal将覆盖parentVal中的数据
  • 如何组合

    watch

    • strats.watch = function (
        parentVal: ?Object,
        childVal: ?Object,
        vm?: Component,
        key: string
      ): ?Object {
      	...格式验证等
        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
      }
       
      • childVal中的属性将循环绑定到parentVal。如果parentVal中存在相同属性,则将parentValchildVal`中对应的属性值插入到数组中,作为
      • 的属性值
  • props|methods|inject|computed连接方法

    • 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
      }
       
      • 联接方式是后者直接覆盖前者相同属性的数据,如果不相同则可以直接链接。
  • 如何搭配

    provide

    • strats.provide = mergeDataOrFn
       
      • 的组合方式与data
      • 相同
// config.optionMergeStrategies
export type Config = {
  optionMergeStrategies: { [key: string]: Function };
};
export default ({
  optionMergeStrategies: Object.create(null), 
}: Config)
 
// defaultStrat 直接覆盖
const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}
 

总结

dataprovide:聚合时,如果目标对象不存在,则属性将直接与目标对象关联;但是,如果目标对象已经存在,则不会进行绑定,但是如果属性对象的属性两个值都是对象,则必须对属性值对象进行递归串联操作。

生命周期:拼接时,将目标对象的钩子函数属性值替换为一个数组,该数组存储了目标对象对应的函数和要拼接的对象对应的函数

component | directive | filter, props | methods | inject | computed:组合后,物品将被绑定。属性是循环的,如果目标对象不存在则直接绑定,如果存在则直接覆盖

watch:合并时,如果目标与合并对象属性相同,则相同属性会合并,不同属性不会合并。串联的主要方法是将属性值替换为一个数组,该数组包含目标对象的方法和要串联的对象对应的函数

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门