在 Vue.js 中使用 Mixin

Avatar of Sarah Drasner
Sarah Drasner

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费积分!

这是一个常见的情况:您有两个非常相似的组件,它们共享相同的基本功能,但它们之间存在足够的差异,使您面临十字路口:我是否将此组件拆分为两个不同的组件? 或者我是否保留一个组件,但使用 props 创建足够的差异,以便我可以更改每个组件?

这两种解决方案都不是完美的:如果您将其拆分为两个组件,则如果功能发生变化,您可能会面临在两个地方更新它的风险,从而破坏 DRY 原则。 另一方面,过多的 props 会很快变得非常混乱,并迫使维护者(即使是您自己)理解大量上下文才能使用它,这会减慢您的速度。

请输入 Mixin。 Vue 中的 Mixin 对于以函数式风格编写很有用,因为最终,函数式编程就是通过减少移动部件来使代码易于理解。(Michael Feathers 关于这一点有一个 很棒的引言)。Mixin 允许您封装一个功能,以便您可以在整个应用程序的不同组件中使用它。 如果编写正确,它们是纯粹的——它们不会修改或更改函数作用域之外的内容,因此您可以可靠地始终使用相同的输入在多次执行中获得相同的值。 这可能非常强大。

基本示例

假设我们有几个不同的组件,它们的作用是切换状态布尔值、模态框和工具提示。 这些工具提示和模态框除了该功能之外没有太多共同点:它们的外观不同,它们的使用方式不同,但逻辑是相似的。

//modal
const Modal = {
  template: '#modal',
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  },
  components: {
    appChild: Child
  }
}

//tooltip
const Tooltip = {
  template: '#tooltip',
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  },
  components: {
    appChild: Child
  }
}

我们可以在这里提取逻辑并创建一个可以重复使用的东西

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

const Modal = {
  template: '#modal',
  mixins: [toggle],
  components: {
    appChild: Child
  }
};

const Tooltip = {
  template: '#tooltip',
  mixins: [toggle],
  components: {
    appChild: Child
  }
};

查看 Sarah Drasner 在 CodePen 上的 Mixin。(@sdras

此示例是为了可读性而故意保持简单和简洁——我发现对实际应用有用的 Mixin 示例包括但不限于:获取视口和组件的尺寸、收集特定鼠标移动事件以及图表的基本元素。 Paul Pflugradt 有一个 不错的 Vue Mixin 仓库,但值得一提的是,它们是用 CoffeeScript 编写的。

用法

此 CodePen 并没有真正说明如何在实际应用程序中设置它,所以让我们接下来看看。

您可以根据自己的喜好设置目录结构,但我喜欢创建一个 mixin 目录以保持井井有条。 我们要创建的文件将具有 .js 扩展名(而不是像其他文件那样使用 .vue),并且我们将导出一个 mixin 对象

directory structure shows mixins in a folder in components directory

然后在 Modal.vue 中,我们可以通过以下方式导入 toggle 来访问它

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

export default {
  name: 'modal',
  mixins: [toggle],
  components: {
    appChild: Child
  }
}

重要的是要了解,即使我们使用的是对象而不是组件,生命周期方法仍然可用。 我们可以在此挂钩到 mounted(),它将应用于组件的生命周期,这使得这种工作方式非常灵活和强大。

合并

查看最后一个示例,我们可以看到,我们不仅拥有我们的功能,而且还拥有来自 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!

如果两者发生冲突,我们可以看到 Vue 实例或组件将获胜

//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 实例字符串的 console.log,而不是一个——这是因为第一个被调用的函数没有被销毁,而是被覆盖了。 我们仍然在这里调用了两个 sayHello() 函数。

全局 Mixin

当我们在引用 Mixin 时使用术语“全局”时,我们并不是指能够在每个组件上访问它们,就像我们在使用过滤器时一样。 我们已经可以使用 mixins: [toggle] 在组件中访问我们的 Mixin。

全局 Mixin 是字面上应用于每个组件。 出于这个原因,它们的用例非常有限,应该谨慎考虑。 我能想到的一个有意义的用例是类似于插件的东西,您可能需要访问所有内容。 但同样,即使在这种情况下,我也会谨慎使用您要应用的内容,尤其是在您将功能扩展到可能是您的黑盒的应用程序时。

要创建全局实例,我们将将其放置在 Vue 实例之上。 在典型的 Vue-cli 构建中,这将放在您的 main.js 文件中。

Vue.mixin({
  mounted() {
    console.log('hello from mixin!')
  }
})

new Vue({
  ...
})

再次提醒,请谨慎使用! 现在,console.log 将出现在每个组件中。 在这种情况下,这并不糟糕(除了控制台中出现的所有噪声)但您可以看到如果使用不当,它可能会造成多大的危害。

结论

Mixin 可用于封装您希望重复使用的一小部分功能。 它们当然不是您唯一可用的选项:例如,高阶组件允许您组合类似的功能,这只是其中一种工作方式。 我喜欢 Mixin,因为我们不必传递状态,但这种模式也可能被滥用,因此请仔细考虑哪个选项最适合您的应用程序。