This rule raises an issue on a Vue component options object that declares a mixins property, for example export default {
mixins: […] } or defineComponent({ mixins: […] }).
This rule only applies to projects on Vue 2.7 or later, where the Composition API is available as a replacement mechanism for reusing logic across components.
Mixins were the primary way to share logic between components in Vue 2. Vue 3 still supports mixins, but the Vue team no longer recommends them: Composition API composables were introduced specifically to replace mixins for logic reuse. A composable is a plain function that is imported and called wherever its logic is needed, which avoids the drawbacks of mixins:
To fix this issue, rewrite the mixin’s logic using the Composition API inside setup() or <script setup>. Extract it
into a separate composable function when the same logic is reused by more than one component; otherwise, writing it directly in the component is
enough. Map each mixin option to its Composition API equivalent:
data properties become ref or reactive state declared in the composable.computed properties become computed() calls.methods become plain functions.on-prefixed equivalents, for example mounted maps to onMounted; logic that
ran in created is usually placed directly in the composable’s body, since it runs as soon as the composable is called.Vue merges a mixin’s hooks and data with the component’s own in a fixed order: a mixin’s hooks run before the component’s own hooks of the same
name. When moving that logic into a composable, call it in the same relative position in setup() so the execution order stays the
same.
// mixins/counter.js
export default {
data() {
return { count: 0 };
},
methods: {
increment() {
this.count++;
},
},
};
// MyComponent.vue
<script>
import counterMixin from './mixins/counter';
export default {
mixins: [counterMixin], // Noncompliant: replace this mixin with a composable
data() {
return { label: 'Count' };
}
};
</script>
// composables/counter.js
import { ref } from 'vue';
export function useCounter() {
const count = ref(0);
function increment() {
count.value++;
}
return { count, increment };
}
// MyComponent.vue
<script>
import { useCounter } from './composables/counter';
export default {
setup() {
const { count, increment } = useCounter();
return { count, increment, label: 'Count' };
}
};
</script>
<script lang="ts">
import { defineComponent } from 'vue';
const counterMixin = {
data() {
return { count: 0 };
},
computed: {
doubleCount(): number {
return this.count * 2;
},
},
methods: {
increment(): void {
this.count++;
},
},
mounted() {
console.log('starting at', this.count);
},
};
export default defineComponent({
mixins: [counterMixin], // Noncompliant: replace this mixin with a composable
});
</script>
<script lang="ts">
import { defineComponent, ref, computed, onMounted } from 'vue';
export default defineComponent({
setup() {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
onMounted(() => {
console.log('starting at', count.value);
});
return { count, doubleCount, increment };
}
});
</script>
Note how the onMounted call sits in setup() at the same relative position where the mixin’s hook used to run, preserving
the execution order described above.
mixins option reference