其他分享
首页 > 其他分享> > Vue中的mixin

Vue中的mixin

作者:互联网

  1. mixin

    Mixin是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin类的方法而不必成为其子类

    Mixin类通常作为功能模块使用,在需要该功能时混入,有利于代码复用又避免了多继承的复杂

    Vue中的mixin

    官方定义:mixin(混入),提供了一种非常灵活的方式,来分发Vue组件中的可复用功能

    本质其实就是一个js对象,它可以包含组件中任意功能选项,如datacomponentsmethodscreatedcomputed等等,只要将共用的功能以对象的方式传入mixins选项中,当组件使用mixins对象时,所有mixins对象的选项都将被混入该组件本身的选项中。

    Vue中可以使用局部混入全局混入

    • 局部混入

      定义一个mixin对象,有组件optionsdatamethods属性

      var myMixin = {
          created: function(){
              this.hello()
          },
          methods:{
              hello:function(){
                  console.log('hello mixin')
              }
          }
      }
      

      组件通过mixins属性调用mixin对象

      Vue.component('componentA',{
          mixins: [myMixin]
      })
      

      该组件在使用的时候,混入了mixin里面的方法,在自动执行create生命钩子,执行hello方法

    • 全局混入

      通过Vue.mixin()进行全局的混入

      Vue.mixin({
          create:function(){
              console.log('全局混入')
          }
      })
      

      使用全局混入需要特别注意,因为它会影响每一个组件实例(包括第三方组件)

      PS:全局混入常用于插件的编写

    • 注意事项

      当组件存在与mixin对象相同的选项时,进行递归合并时组件的选项会覆盖mixin的选项,但是如果相同选项为生命周期钩子时,会合并成一个数组,先执行mixin的钩子,再执行组件的钩子。

  2. 使用场景

    在日常的开发中,经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立。这时,可以通过Vuemixin功能将相同或者相似的代码提取出来。

    举个栗子:

    定义一个model弹窗组件,内部通过isShowing来控制显示

    const Model = {
        template: '#model',
        data(){
            return {
    			isShowing:false
            }
        },
        methods:{
            toggleShow(){
                this.isShowing = !this.isShowing
            }
        }
    }
    

    定义一个toolTip提示框,内部通过isShowing来控制显示

    const toolTip = {
        template:'#tooltip',
        data(){
            return{
                isShowing:false
            }
        },
        methods:{
            toggleShow(){
                this.isShowing = !isShowing
            }
        }
    }
    

    通过观察上面两个组件,发现两者的逻辑是相同的,代码控制显示也是相同的,这时mixin就派上用场了。

    首先抽取出共同代码,编写一个mixin

    const toggle = {
        data(){
            return{
                isShowing:false
            }
        },
        methods:{
            toggleShow(){
                this.isShowing = !isShowing
            }
        }
    }
    

    两个组件在使用上,只需要引入mixin

    const Model = {
        template:'#model',
        mixins:[toggle]
    }
    
    const toolTip = {
        template:'#tootip',
        mixins:[toggle]
    }
    
  3. 源码分析

    首先从Vue.mixin入手

    源码位置:/src/core/global-api/mixin.js

    export function initMixin (Vue: GlobalAPI) {
      Vue.mixin = function (mixin: Object) {
        this.options = mergeOptions(this.options, mixin)
        return this
      }
    }
    

    主要是调用mergeOptios方法

    源码位置:/src/core/util/options.js

    export function mergeOptions (
      parent: Object,
      child: Object,
      vm?: Component
    ): Object {
    
    if (child.mixins) { // 判断有没有mixin 也就是mixin里面挂mixin的情况 有的话递归进行合并
        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) // 先遍历parent的key 调对应的strats[XXX]方法进行合并
      }
      for (key in child) {
        if (!hasOwn(parent, key)) { // 如果parent已经处理过某个key 就不处理了
          mergeField(key) // 处理child中的key 也就parent中没有处理过的key
        }
      }
      function mergeField (key) {
        const strat = strats[key] || defaultStrat
        options[key] = strat(parent[key], child[key], vm, key) // 根据不同类型的options调用strats中不同的方法进行合并
      }
      return options
    }
    

    从上面的源码,可以得到以下几点:

    • 优先递归处理mixins
    • 先遍历合并parent中的key,调用mergeField方法进行合并,然后保存在变量options
    • 再遍历child,合并补上parent中没有的key,调用mergeField方法进行合并,保存在变量options
    • 通过mergeField函数进行了合并

    下面是关于Vue的几种类型的合并策略

    • 替换性

      替换型合并有propsmethodsinjectcomputed

      strats.props =
      strats.methods =
      strats.inject =
      strats.computed = function (
        parentVal: ?Object,
        childVal: ?Object,
        vm?: Component,
        key: string
      ): ?Object {
        if (!parentVal) return childVal // 如果parentVal没有值,直接返回childVal
        const ret = Object.create(null) // 创建一个第三方对象 ret
        extend(ret, parentVal) // extend方法实际是把parentVal的属性复制到ret中
        if (childVal) extend(ret, childVal) // 把childVal的属性复制到ret中
        return ret
      }
      strats.provide = mergeDataOrFn
      

      同名的propsmethodsinjectcomputed会被后来者代替

    • 合并型

      合并型合并有data

      strats.data = function(parentVal, childVal, vm) {    
          return mergeDataOrFn(
              parentVal, childVal, vm
          )
      };
      
      function mergeDataOrFn(parentVal, childVal, vm) {    
          return function mergedInstanceDataFn() {        
              var childData = childVal.call(vm, vm) // 执行data挂的函数得到对象
              var parentData = parentVal.call(vm, vm)        
              if (childData) {            
                  return mergeData(childData, parentData) // 将2个对象进行合并                                 
              } else {            
                  return parentData // 如果没有childData 直接返回parentData
              }
          }
      }
      
      function mergeData(to, from) {    
          if (!from) return to    
          var key, toVal, fromVal;    
          var keys = Object.keys(from);   
          for (var i = 0; i < keys.length; i++) {
              key = keys[i];
              toVal = to[key];
              fromVal = from[key];    
              // 如果不存在这个属性,就重新设置
              if (!to.hasOwnProperty(key)) {
                  set(to, key, fromVal);
              }      
              // 存在相同属性,合并对象
              else if (typeof toVal =="object" && typeof fromVal =="object") {
                  mergeData(toVal, fromVal);
              }
          }    
          return to
      }
      

      mergeData函数遍历了要合并的data的所有属性,然后根据不同情况进行合并:

      • 当目标data对象不包含当前属性时,调用set方法进行合并(set方法其实就是一些合并重新赋值的方法)
      • 当目标data对象包含当前属性并且当前值为纯对象时,递归合并当前对象值,这样做是为了防止对象存在新增属性
    • 队列性

      队列型的合并有全部的生命周期watch

      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(hook => {
        strats[hook] = mergeHook
      })
      
      // watch
      strats.watch = function (
        parentVal,
        childVal,
        vm,
        key
      ) {
        // work around Firefox's Object.prototype.watch...
        if (parentVal === nativeWatch) { parentVal = undefined; }
        if (childVal === nativeWatch) { childVal = undefined; }
        /* istanbul ignore if */
        if (!childVal) { return Object.create(parentVal || null) }
        {
          assertObjectType(key, childVal, vm);
        }
        if (!parentVal) { return childVal }
        var ret = {};
        extend(ret, parentVal);
        for (var key$1 in childVal) {
          var parent = ret[key$1];
          var child = childVal[key$1];
          if (parent && !Array.isArray(parent)) {
            parent = [parent];
          }
          ret[key$1] = parent
            ? parent.concat(child)
            : Array.isArray(child) ? child : [child];
        }
        return ret
      };
      

      生命周期钩子和watch被合并为一个数组,然后正序遍历一次执行

    • 叠加型

      叠加型合并有componentdirectivesfilters

      strats.components=
      strats.directives=
      
      strats.filters = function mergeAssets(
          parentVal, childVal, vm, key
      ) {    
          var res = Object.create(parentVal || null);    
          if (childVal) { 
              for (var key in childVal) {
                  res[key] = childVal[key];
              }   
          } 
          return res
      }
      

      叠加型主要是通过原型链进行层层的叠加

  4. 小结

    • 替换型策略有propsmethodsinjectcomputed,就是将新的同名参数替代旧的参数
    • 合并型策略是data,通过set方法进行合并和重新赋值
    • 队列型策略有生命周期函数watch,原理是将函数存入一个数组,然后正序遍历依次执行
    • 叠加型有componentdirectivesfilters,通过原型链进行层层的叠加

参考文献:

https://vue3js.cn/interview/vue/mixin.html

标签:Vue,return,mixin,parent,childVal,parentVal,key
来源: https://www.cnblogs.com/shallow-dreamer/p/16552807.html