我看vue之mixins助力项目开发

背景

我们通常会遇到这么一个场景:有几个基本功能一样的组件,但是他们之间又存在着足够的差异。这时候你就来到了一个岔路口:我是把他们“按部就班”地写成不同的组件呢?还是保留为一个“公共组件”,然后通过props传参进行不同功能之间的区分呢?

现在还有一个场景:在一些组件(甚至是项目中全部和某个功能有关的组件)中,有某个功能是相同的。而且都需要利用这个功能进行后续操作。你又需要选择了,但是这次有一个前提:肯定是要“复用”的 —— 公共组件?还是 mixin

我们现在来分析下:
在第一个场景中,其实两种解决方案都不够完美:如果拆分成多个组件,你就不得不冒着一旦功能变动就要在所有相关文件中更新代码的风险,这违背了 DRY 原则;反之,太多的 props 传值会让代码变得混乱不堪,后续难以维护、团队理解困难,效率降低。
而在第二个场景中,其实我们很清楚地看到:这时候我们需要的不是一个可以传值的组件,而是一个类似于插件一样的 js 代码(这么说能够理解吧)!

使用Mixin吧

Vue 中的 Mixin 对编写函数式风格的代码很有用,因为函数式编程就是通过减少移动的部分让代码更好理解。Mixin 允许你封装一块在应用的其他组件中都可以使用的函数。如果使用姿势得当,他们不会改变函数作用域外部的任何东西。因此哪怕执行多次,只要是同样的输入你总是能得到一样的值。

如何使用

mixin其实有两种写法 —— ObjectFunction
它们都可以在单个组件或者全局中引用。但对于 function 形式的mixin,笔者更推荐将其作为组件级别使用(而非全局的)。

先看第一种写法:
假设有一对不同的组件,它们的作用是通过切换状态(Boolean)来展示或者隐藏模态框或提示框。这些提示框和模态框除了功能相似以外,没有其他共同点:它们看起来不一样,用法不一样,但是逻辑一样。
这时我们可以将它们的公共逻辑部分封装为一个js文件:

// mixins目录下的toggle.js文件
export const toggle = {
    data() {
        return {
            isShowing: false
        }
    },
    methods: {
        toggleShow() {
            this.isShowing = !this.isShowing;
        }
    }
}

一般我们选择新建一个专门的mixin目录。在里面创建一个文件含有.js扩展名,为了使用Mixin我们需要输出一个对象。(es6 Modules)

然后使用mixins:[] 的方式引入mixin文件,(引入后)对象中的属性可直接使用(就像开头说的“插件”一样):

import { toggle } from './mixins/toggle';

//...
const Modal = {
    template: '#modal',
    mixins: [toggle],
    //...
};

const Tooltip = {
    template: '#tooltip',
    mixins: [toggle],
    //...
};

第二种写法:
这种形式其实就特别适用于开头说的第二种情况。因为 mixin 内部一个组件该有的它都可以具备。而且上面也说了:当mixin被引入后它内部的东西可以被直接使用 —— 其实就是被merge到引用它的组件中了!(相当于对父组件的扩展)

假如我们请求完要根据数据给出提示并且要给出降级方案(默认提示)。这个需求基本是项目中必不可少而且不止一次出现的。但是像一般情况没有引用其余外部UI而且又不是模态框那样的“通用提示”,放在全局中不太合适。这时候就需要我们的 mixin 出场了:

// mixins目录下的index.js文件
function formatRes(res) {
    const data = res.data;
    if (data.status.code === '表示通过的数') {
      return data
    } else {
      if (判断是否引入了提示框组件) {
        //提示框组件的调用和传参
      }
      return data
  
    }
  }
  
  var mixin = function (options) {
    let defaultData = {}
    let defaultMethods = {}
  
    defaultMethods.formatRes = formatRes;
    return {
      data: function () {   //这个会在引用它的组件的data中出现
        return defaultData;
      },
      methods: defaultMethods,   //同上,在引用它的组件中可直接通过this.formatRes调用到
    }
  }
  
  export default mixin

因为是函数形式,所以在引用vue的script开头应该这么写:

import mixin from '../mixin/index'
const mixinCommen = mixin();
//在export default中这么写:
mixins: [mixinCommen],

使用:

const res = await this.$http({   //封装的请求库
    method: 'GET',
    url: '请求地址',
    params: {
        param: {
        }
    }
})
const {result} = this.formatRes(res)   // 使用mixins函数
if(result) {
    this.areaList = result
} else {
    //...
    return false
}
return true

闭包!一方面让外部函数可以接收参数,另一方面函数内暴露对象的写法和vue组件中data必须是函数的原理一样 —— 让一个地方的修改不影响其余地方的数据。

合并和冲突

在下面的这个例子,我们可以看到,我们不仅仅是实现了自己想要的功能,并且 Mixin 中的生命周期的钩子也同样是可用的。因此,当我们在组件上应用 Mixin 的时候,有可能会有钩子的执行顺序的问题。默认 Mixin 上会首先被注册,组件上的接着注册,这样我们就可以在组件中按需要重写 Mixin 中的语句。组件拥有最终发言权。

//mixin
const hi = {
    mounted() {
        console.log('hello from mixin!')
    }
}

//vue instance or component
new Vue({
    el: '#app',
    mixins: [hi],
    mounted() {
        console.log('hello from Vue instance!')
    }
});

//Output in console
> hello from mixin!
> hello from Vue instance!
//mixin
const hi = {
    methods: {
        sayHello: function() {
            console.log('hello from mixin!')
        }
    },
    mounted() {
        this.sayHello()
    }
}

//vue instance or component
new Vue({
    el: '#app',
    mixins: [hi],
    methods: {
        sayHello: function() {
            console.log('hello from Vue instance!')
        }
    },
    mounted() {
        this.sayHello()
    }
})

// Output in console
> hello from Vue instance!
> hello from Vue instance!

其实在vue的源码中,我们可以很清楚的看到:mergeOptions 会去遍历 mixins ,parent 先和 mixins 合并,然后才去和 child 合并

function mergeOptions(parent, child, vm) {    
    if (child.mixins) {        

        for (var i = 0, l = child.mixins.length; i < l; i++) {
            parent = mergeOptions(parent, child.mixins[i], vm);
        }
    }    
    //...
}

而对于生命周期来说,vue会把所有的钩子函数保存进一个数组。并顺序执行(清空这个数组)。
在这里面,混入对象的钩子会在组件自身的钩子之前被调用。如果两者有重复,则组件的方法将会重写mixin里的方法 —— methods、props等等也是一样!

来源url
栏目