JavaScript

Why Mixins are Considered Harmful

Composition over Inheritance

Fotis Adamakis
Fotis Adamakis
Senior Software Engineer / Technical Writer
5 min read
January 31, 2023

Why Mixins are Considered Harmful

Composition over Inheritance

Why Mixins are Considered Harmful

Mixins were introduced in Vue 2 as a solution for sharing code between components and became an integral part of many codebases. However, as time passed, issues began to arise with their usage. Despite their initial appeal, mixins are now considered to be harmful by many developers. In this article, we will explore the drawbacks and a better way to share code that was introduced recently.

Drawbacks

Complexity

Mixins can add an excessive amount of logic to a component, making it more complex and harder to understand, leading to unintended consequences and difficulties in maintenance. For instance, if a component has multiple mixins that each bring their own properties and methods, the component will have a lot of logic, and it will be hard to determine how each property and method affect the component’s behaviour. This over-complication of the component due to mixins makes it challenging to extend or debug.

To avoid this, it’s crucial to choose mixins wisely and make sure they simplify rather than complicate the component’s logic. Mixins can bring both accidental and essential complexity to a component. Accidental complexity refers to the unnecessary complexity that can be eliminated without affecting the functionality, while essential complexity is inherent in solving a problem and cannot be avoided. It’s important to consider both types of complexity while using mixins.

Naming conflicts and ambiguous merging strategy

Mixins can have overlapping properties with the component, leading to naming conflicts and unexpected behaviour.

Imagine having a mixin with an employee-specific logic that, among others, declares the VAT amount.

// mixins/employee.js
export default {
  data() {
    return {
      vat: 20
    };
  },
  computed: {
    calculateSalary() {
      return this.baseSalary - (this.baseSalary \* this.vat) / 100;
    }
  }
};

And a Salary display component that also declares the VAT amount

<template>
  <div id="app">Salary: {{ calculateSalary }}</div>
</template>

<script>
import employee from "./mixins/employee.js";

export default {
  name: "App",
  mixins: [employee],
  data() {
    return {
      baseSalary: 100000,
      vat: 30,
    };
  },
};
</script>

Outputs Salary: 70000

Try it yourself

In this case, the VAT amount declared right next to the calculate function will be ignored and in a real-life example with more complex logic, this is hard to spot, and if you are unit testing the mixin in isolation (like you should), you might get a false positive.

This can be even more confusing when naming conflicts between two different mixins. In that case, the one declared last overwrites the other!

[More about vue mixin merging strategy in the official docs](https://v2.vuejs.org/v2/guide/mixins.html?redirect=true#Option-Merging)

Understanding component behaviour

Mixins can make it difficult to understand a component’s behaviour because it can be unclear which properties and methods are coming from the component and which are coming from the mixin. This can lead to unexpected behaviour and make it harder to maintain the component.

In the previous example, the template uses a calculateSalary function that is not declared or explicitly imported anywhere. In complex components with multiple mixins, this can be very confusing.

Anyone can write code that a computer can understand. Good programmers write code that humans can understand.

Decreased reusability

Mixins make it harder to reuse components because they increase the component’s dependencies and make it more tightly coupled to specific functionality.

For example, imagine a component using a mixin that provides specific styling for that component. If another component also wants to use the same styling, it will have to use the same mixin. This means that the component’s styling is now tightly coupled to the mixin and cannot be reused without also using the mixin.

This makes the component less reusable because it cannot be used in other parts of the application without also including the mixin and its dependencies. This can lead to code duplication and make the application harder to maintain.

The Solution

Composables

Composition API is the highlight of Vue 3. The primary advantage is enabling clean, efficient logic reuse in the form of Composable functions. It solves the drawbacks of mixins and enables the creation of impressive community projects such as VueUse. Additionally, it serves as a clean mechanism for easily integrating stateful third-party services or libraries into Vue’s reactivity system, for example, immutable data, state machines, and RxJS.

As we discussed earlier, using mixins creates highly coupled and hard-to-extend or maintain code.

A different approach is to have each component explicitly import all of its dependencies. This way, everything is transparent and easy to reason about. Naming collisions are eliminated by design, and testing is more straightforward.

It’s important to note that a bundler like webpack or rollup will ensure each dependency is optimised and only imported once when needed.

In practice, the Composition API is a set of utility helpers that enable us to author our components using explicitly imported functions instead of declaring options. It is an umbrella term that covers the following APIs:

  • [Reactivity API](https://vuejs.org/api/reactivity-core.html), e.g. ref() and reactive(), which allows us to directly create the reactive state, computed state, and watchers.
  • [Lifecycle Hooks](https://vuejs.org/api/composition-api-lifecycle.html), e.g. onMounted() and onUnmounted(), which allows us to hook into the component lifecycle programmatically.
  • [Dependency Injection](https://vuejs.org/api/composition-api-dependency-injection.html), i.e. provide() and inject(), which allows us to leverage Vue’s dependency injection system while using Reactivity APIs.

A very basic example of a composable is the following:

// mouse.js
import { ref, onMounted, onUnmounted } from "vue";

// by convention, composable function names start with "use"
export function useMouse() {
  // state encapsulated and managed by the composable
  const x = ref(0);
  const y = ref(0);

  // a composable can update its managed state over time.
  function update(event) {
    x.value = event.pageX;
    y.value = event.pageY;
  }

  // a composable can also hook into its owner component's
  // lifecycle to setup and teardown side effects.
  onMounted(() => window.addEventListener("mousemove", update));
  onUnmounted(() => window.removeEventListener("mousemove", update));

  // expose managed state as return value
  return { x, y };
}
<script setup>
import { useMouse } from "./mouse.js";

const { x, y } = useMouse();
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>

Source

All the mouse tracking logic is encapsulated inside the composable, which consists primarily of simple javascript logic enhanced by the Vue reactivity magic. ✨

For additional composable use cases, take a look at the VueUse library, Refactoring a Component from Vue 2 Options API to Vue 3 Composition API or any other of my Vue 3 related articles.

Additional Resources

Mixins Considered Harmful - React Blog

Composition API FAQ | Vue.js

Fotis Adamakis

Fotis Adamakis

Senior Software Engineer / Technical Writer

Experienced software engineer writing about front end architecture, accessibility, system design, and developer productivity. Lessons from building and maintaining large-scale frontend applications, with a focus on practical patterns that make codebases easier to understand, scale, and evolve.

Barcelona, Spain