Vue.js 3 Mixins Tutorial
In this Vue tutorial we learn how to create code files that we can use across multiple components.
We cover how to create and use mixins, their config object merging priorities and some issues with their usage.
Lesson Video
If you prefer to learn visually, you can watch this lesson in video format.
Lesson Project
If you want to follow along with the examples, you will need an app generated by the Vue CLI as well as the following extra component.
- src/components/ClickCounter.vue
- src/components/HoverCounter.vue
The project should look similar to the following.
project-folder/
├── src/
| ├── components/
| | ├── ClickCounter.vue
| | └── HoverCounter.vue
| └── App.vue
We’ll set up the components throughout the lesson.
What is a Mixin?
A mixin is a Vue object in a separate file that contains code we want to reuse across multiple components.
It’s a Javascript (or TypeScript) file that may contain any Vue option, like data, methods, computed properties etc.
tip Composables are generally preferend over mixins, but they use the Composition API .
As an example, let’s say our ClickCounter component tracks a counter based on the number of clicks on a button.
<template>
<button @click="increment">Clicks: {{ count }}</button>
</template>
<script>
export default {
data() {
return { count: 0 }
},
methods: {
increment() { this.count++ }
}
}
</script>
And our HoverCounter component tracks a counter based on the number of times an element was hovered over.
<template>
<div @mouseover="increment">Hovers: {{ count }}</div>
</template>
<script>
export default {
data() {
return { count: 0 }
},
methods: {
increment() { this.count++ }
}
}
</script>
<style scoped>
div{background:gainsboro}
</style>
If we add them to the root App component and take a look in the browser, they both work fine.
<template>
<click-counter />
<hr>
<hover-counter />
</template>
<script>
import ClickCounter from '@/components/ClickCounter'
import HoverCounter from '@/components/HoverCounter'
export default {
components: {
ClickCounter,
HoverCounter
}
}
</script>
The problem is that we’re duplicating code. Both components have the exact same script block.
A mixin allows us to define the tracking code once in an external file, then import and use it in both components.
How to create a mixin
As mentioned earlier, a mixin is just a Javascript (or TypeScript) file with a Vue component config object.
export default {
data() {
return { /* data */ }
},
methods: {
some_method() { }
},
// etc.
}
We can export the component directly (like above), or we can store it in a variable and export that variable.
const object_name = {
data() {
return { /* data */ }
},
methods: {
some_method() { }
},
// etc.
}
export default object_name
The only difference between the two is how we import them. An unnamed export can use any name of our choosing in the import, but a named export must be imported with the same name that we exported.
// unnamed
import AnyName from 'path-to-mixin'
// named
import object_name from 'path-to-mixin'
note It doesn’t really matter which type of export you use, but try to stay consistent throughout your project.
As an example, let’s create a /src/mixins/counter.js mixin.
Inside it, we’ll have the same data property and method that the counter components have.
const counter = {
data() {
return { count: 0 }
},
methods: {
increment() { this.count++ }
}
}
export default counter
How to use a mixin in a component
To use a mixin in a component, we import and specify it in an option called mixins, which takes an array as value.
// import mixin
import mixin_name from 'path-to-mixin'
export default {
// add to mixins array
mixins: ['mixin_name']
}
Vue will take the mixin object, and merge it with the object in the component.
As an example, let’s import our newly created counter mixin and import it into the two counter components.
We can remove the previous data properties and methods because they will come from the mixin now.
<template>
<button @click="increment">Clicks: {{ count }}</button>
</template>
<script>
import counter from '@/mixins/counter'
export default {
mixins: [counter]
}
</script>
<template>
<div @mouseover="increment">Hovers: {{ count }}</div>
</template>
<script>
import counter from '@/mixins/counter'
export default {
mixins: [counter]
}
</script>
<style scoped>
div{background:gainsboro}
</style>
If we save the files and take a look in the browser, the counters still work as expected.
Config option merging priority
As we mentioned earlier, Vue will merge the options in a mixin with the options in a component.
When a component contains the same option as the mixin, the component option takes priority.
As an example, let’s add the the count data property to the ClickCounter component with a default value of 50.
<template>
<button @click="increment">Clicks: {{ count }}</button>
</template>
<script>
import counter from '@/mixins/counter'
export default {
data() {
return { count: 50 }
},
mixins: [counter]
}
</script>
If we save the file and take a look in the browser, the button will show 50 as the count, but still increments when we click on it.
So Vue used the count from the component instead of the mixin.
The problem with mixins
At the start of the lesson we mentioned that composables are typically favored above mixins. That’s because mixins can cause some issues.
- The first problem is that we can’t pass any data to a mixin to customize it, so it’s not as reusable as it seems at first.
The second is that it’s not always obvious which options are in a component, or what’s available on the component instance ( this ).
For example, a team member could define a key in the data option that’s the same as the key in a mixin, causing unexpected behavior or even breaking the app.
The Composition API solves these problems by defining a function that modifies data and returns it. And because we can use both the Options and Composition APIs, composables are the better choice.
note If you’re a Vue beginner, don’t worry about composables right now. They won’t make any sense until you understand the Composition API, which we cover in detail later on.