Why is this an issue?

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:

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.

Code examples

Noncompliant code example

// 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>

Compliant solution

// 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>

Noncompliant code example

<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>

Compliant solution

<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.

Resources

Documentation