Mixins

Basics

Mixins distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be "mixed" into the component's own options.

Example:

// define a mixin object
const myMixin = {
  created() {
    this.hello()
  },
  methods: {
    hello() {
      console.log('hello from mixin!')
    }
  }
}

// define an app that uses this mixin
const app = Vue.createApp({
  mixins: [myMixin]
})

app.mount('#mixins-basic') // => "hello from mixin!"

Option Merging

When a mixin and the component itself contain overlapping options, they will be "merged" using appropriate strategies.

For example, each mixin can have its own data function. Each of them will be called, with the returned objects being merged. Properties from the component's own data will take priority in cases of conflicts.

const myMixin = {
  data() {
    return {
      message: 'hello',
      foo: 'abc'
    }
  }
}

const app = Vue.createApp({
  mixins: [myMixin],
  data() {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created() {
    console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})

Hook functions with the same name are merged into an array so that all of them will be called. Mixin hooks will be called before the component's own hooks.

const myMixin = {
  created() {
    console.log('mixin hook called')
  }
}

const app = Vue.createApp({
  mixins: [myMixin],
  created() {
    console.log('component hook called')
  }
})

// => "mixin hook called"
// => "component hook called"

Options that expect object values, for example methods, components and directives, will be merged into the same object. The component's options will take priority when there are conflicting keys in these objects:

const myMixin = {
  methods: {
    foo() {
      console.log('foo')
    },
    conflicting() {
      console.log('from mixin')
    }
  }
}

const app = Vue.createApp({
  mixins: [myMixin],
  methods: {
    bar() {
      console.log('bar')
    },
    conflicting() {
      console.log('from self')
    }
  }
})

const vm = app.mount('#mixins-basic')

vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"

Global Mixin

You can also apply a mixin globally for a Vue application:

const app = Vue.createApp({
  myOption: 'hello!'
})

// inject a handler for `myOption` custom option
app.mixin({
  created() {
    const myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

app.mount('#mixins-global') // => "hello!"

Use with caution! Once you apply a mixin globally, it will affect every component instance created afterwards in the given app (for example, child components):

const app = Vue.createApp({
  myOption: 'hello!'
})

// inject a handler for `myOption` custom option
app.mixin({
  created() {
    const myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

// add myOption also to child component
app.component('test-component', {
  myOption: 'hello from component!'
})

app.mount('#mixins-global')

// => "hello!"
// => "hello from component!"

In most cases, you should only use it for custom option handling like demonstrated in the example above. It's also a good idea to ship them as Plugins to avoid duplicate application.

Custom Option Merge Strategies

When custom options are merged, they use the default strategy which overwrites the existing value. If you want a custom option to be merged using custom logic, you need to attach a function to app.config.optionMergeStrategies:

const app = Vue.createApp({})

app.config.optionMergeStrategies.customOption = (toVal, fromVal) => {
  // return mergedVal
}

The merge strategy receives the value of that option defined on the parent and child instances as the first and second arguments, respectively. Let's try to check what do we have in these parameters when we use a mixin:

const app = Vue.createApp({
  custom: 'hello!'
})

app.config.optionMergeStrategies.custom = (toVal, fromVal) => {
  console.log(fromVal, toVal)
  // => "goodbye!", undefined
  // => "hello", "goodbye!"
  return fromVal || toVal
}

app.mixin({
  custom: 'goodbye!',
  created() {
    console.log(this.$options.custom) // => "hello!"
  }
})

As you can see, in the console we have toVal and fromVal printed first from the mixin and then from the app. We always return fromVal if it exists, that's why this.$options.custom is set to hello! in the end. Let's try to change a strategy to always return a value from the child instance:

const app = Vue.createApp({
  custom: 'hello!'
})

app.config.optionMergeStrategies.custom = (toVal, fromVal) => toVal || fromVal

app.mixin({
  custom: 'goodbye!',
  created() {
    console.log(this.$options.custom) // => "goodbye!"
  }
})

Drawbacks

In Vue 2, mixins were the primary tool to abstract parts of component logic into reusable chunks. However, they have a few issues:

  • Mixins are conflict-prone: Since properties from each mixin are merged into the same component, you still have to know about every other mixin to avoid property name conflicts.

  • Properties seem to appear from nowhere: If a component uses multiple mixins it isn't necessarily obvious which properties came from which mixin.

  • Reusability is limited: we cannot pass any parameters to the mixin to change its logic, which reduces their flexibility in terms of abstracting logic.

To address these issues, we added a new way to organize code by logical concerns: the Composition API.

© 2013–present Yuxi Evan You
Licensed under the MIT License.
https://v3.vuejs.org/guide/mixins.html