Your search - - did not match any documents.
Suggestions:
It’s common when developing an application full of components to reach a point where you have two components that contain very similar functionality. The question then is: do we fuse those two components into one? Or do we keep them separate since there’s enough different about each of them that combining them would cause more problems than it would solve?
Fortunately, Vue gives us a third option: Mixins. Mixins are a useful way to encapsulate a small piece of functionality that can be reused across multiple components. In this tutorial, we’ll be learning how to create and efficiently make use of this handy tool.
To start getting familiar with Mixins, let’s look at a simple example.
📃 exampleMixin.js
export const exampleMixin = {
created() {
console.log('Hello from the mixin!')
}
}
As you can see, we’ve created a new .js file that exports our exampleMixin, which just logs a message to the console when the component is created
.
Now we can import this mixin into an example component and list it on that component’s mixins
option in order to extend that component’s functionality.
📃Example.vue
<script>
import { exampleMixin } from '../mixins/exampleMixin.js'
export default {
mixins: [exampleMixin]
}
</script>
Now, when our Example component is created
, we should see the console.log from our exampleMixin appear.
Great. So far so good.
The way that mixins are handled, the mixin code is run prior to the component code. To see this in action, let’s look at how this code will run:
If we have this same mixin:
📃 exampleMixin.js
export const exampleMixin = {
created() {
console.log('Hello from the mixin!')
}
}
And this component:
📃Example.vue
<script>
import { exampleMixin } from '../mixins/exampleMixin.js'
export default {
mixins: [exampleMixin],
created() {
console.log('Hey from the component.')
}
}
</script>
Now our component itself is also supposed to be logging a message to the console when it is created
. So what do you think we’ll see in the console? In what order do you expect to see the logs?
If we check the console, we see:
Indeed, our mixin code was run just before our component’s code, as we can see by the order of our console logs. Under the hood, both of our created
hook functions were merged into an array, with the mixin’s hook being the first element in that array and our component’s hook being the second. Then they were run in that order, hence the order in which we see the console logs.
Now what if needed to mix-in more than just a simple lifecycle hook? We aren’t limited to just hooks, we can mix-in any component option, such as methods, data, props, etc.
For example, we might have a mixin that looks like this:
exampleMixin.js
export const exampleMixin = {
created() {
this.logMessage()
},
data() {
return {
message: 'I am such a nice mixin.'
}
},
methods: {
logMessage() {
console.log(this.message)
}
}
}
Now if we imported this into a component, it would run the logMessage
method when it was created
, printing the message
data to the console. However, that is assuming none of what is on this mixin conflicts with what is on our component. Fortunately, the way Vue handles mixins helps us automatically avoid conflicts when they arise. Let’s look at how that works.
In cases where we want to mix-in data, data objects undergo a recursive merge. If there are conflicts, the component’s data takes priority. Let’s look at an example.
If we had this mixin:
📃exampleMixin.js
export const exampleMixin = {
data() { return { message: 'I am secondary.' } }
}
And this component:
📃Example.vue
<script>
import { exampleMixin } from '../mixins/exampleMixin.js'
export default {
mixins: [exampleMixin],
data() {
return {
message: 'I take priority.'
}
},
created() {
console.log(this.message)
}
}
</script>
Which message
do you think will show up in the console? Looking in the console, we’ll see that the component’s data won out.
Vue recognized there was a conflict between the mixin’s message
data and the component’s message
data, so it chose to use the component’s own code. Again, the component’s code will always take priority in conflicts like this.
Now that we understand how hooks and data are merged, what about options that expect an object such as our methods
or props
option? As we might expect by now, the component’s options will take priority when there are conflicting keys in those merged objects.
For example, if we had this mixin, which has a hello
method:
📃exampleMixin.js
export const exampleMixin = {
methods: {
hello() {
console.log('Hello from the mixin!')
}
}
}
And this component, which has its own hello
method:
📃Example.vue
<script>
import { exampleMixin } from '../mixins/exampleMixin.js'
export default {
mixins: [exampleMixin],
methods: {
hello() {
console.log('Hello from the component.')
}
}
}
</script>
What will we see in the console?
That’s right - we’d see the output of our component’s hello
method since that took priority over the conflicting hello
method from our mixin.
It’s also worth noting that we are able to create global mixins, which would be applied to each and every component within our application.
In a Vue CLI-generated project, we’d create a global mixin in our main.js file, just above the main Vue instance.
📃main.js
Vue.mixin({
mounted() {
console.log('I am mixed into every component.')
}
})
new Vue({
...
})
However, the use cases for a global mixin are rare and it is highly recommended that you use global mixins with extreme caution or simply avoid using them at all since they will be affecting literally every component at every level of your app.
Now that we understand the basics of how mixins work to extend the functionality of our components, let’s look at an example that’s more… Next-Level.
Specifically, if we look at how we’ve written our BaseSelect and BaseInput components, we’ll notice that we’re repeating some code within each of them.
📃BaseSelect.vue
<template>
<div>
<label v-if="label">{{ label }}</label>
<select :value="value" @change="updateValue" v-bind="$attrs" v-on="$listeners">
<option
v-for="option in options"
:value="option"
:key="option.id"
:selected="option === value"
>{{ option }}</option>
</select>
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
options: {
type: Array,
required: true
},
value: [String, Number],
label: String
},
methods: {
updateValue(event) {
this.$emit('input', event.target.value)
}
}
}
</script>
📃BaseInput.vue
<template>
<div>
<label v-if="label">{{ label }}</label>
<input :value="value" @input="updateValue" v-bind="$attrs">
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
label: {
type: String,
default: ''
},
value: [String, Number]
},
methods: {
updateValue(event) {
this.$emit('input', event.target.value)
}
}
}
</script>
Both components contain inheritAttrs: false,
along with a label
and value
prop and an updateValue
method. We can encapsulate this repetitive code into its own mixin that both form components can then use.
Before we create our mixin, we’ll add a mixins folder within our src folder to keep things organized. Inside of our mixins folder, we’ll create a .js file for our new mixin.
📃src/mixins/formFieldMixin.js
export const formFieldMixin = {
inheritAttrs: false,
props: {
label: {
type: String,
default: ''
},
value: [String, Number]
},
methods: {
updateValue(event) {
this.$emit('input', event.target.value)
}
}
}
As you can see, we’ve added all of the code that was repeated between the two components.
Since we have now encapsulated that repeated code within a new formFieldMixin, we can import it into our BaseInput and BaseSelect components, and delete the code that the mixin replaces.
📃src/components/BaseInput.vue
<template>
<div>
<label v-if="label">{{ label }}</label>
<input :value="value" @input="updateValue" v-bind="$attrs" v-on="listeners">
</div>
</template>
<script>
import { formFieldMixin } from '../mixins/formFieldMixin' // import mixin
export default {
mixins: [formFieldMixin], // register mixin
props: {
value: [String, Number]
},
computed: {
listeners() {
return {
...this.$listeners,
input: this.updateValue
}
}
}
}
</script>
📃src/components/BaseSelect.vue
<template>
<div>
<label v-if="label">{{ label }}</label>
<select :value="value" @change="updateValue" v-bind="$attrs" v-on="$listeners">
<option
v-for="option in options"
:value="option"
:key="option.id"
:selected="option === value"
>{{ option }}</option>
</select>
</div>
</template>
<script>
import { formFieldMixin } from '../mixins/formFieldMixin' // import mixin
export default {
mixins: [formFieldMixin], // register mixin
props: {
options: {
type: Array,
required: true
},
value: [String, Number]
}
}
</script>
Great. Now these form components appear even leaner and both rely on the same shared functionality of our formFieldMixin, making our code more DRY.
Mixins are a handy way to reuse code across our components to share behavior, making our codebase less repetitive and more modular. We looked at the order of operations of mixins, and we learned how mixin code is run prior to the component’s own code and how that helps automatically resolve conflicts that may emerge with things such as duplicated method names. We then saw how easy it can be to encapsulate code that is repeated within two places into a single mixin instead.
In the next lesson, we’ll take a look at ways to effectively filter within our components.