Want to share code between your Vue components? If you are familiar with Vue 2, you may know how to use mixin, but the new Composition API provides a better solution.
In this article, we will look at the shortcomings of mixins and see how the Composition API overcomes them and make Vue applications more scalable.
Review of Mixins features
Let's quickly review the mixins mode, because for what we're going to talk about in the next section, be sure to put it first.
Typically, a Vue component is defined by a JavaScript object that has various properties representing our required functions—such as data, methods, computed, etc.
// export default { data: () => ({ myDataProperty: null }), methods: { myMethod () { ... } } // ... }
When we want to share the same properties between components, we can extract the common properties into a separate module:
// export default { data: () => ({ mySharedDataProperty: null }), methods: { mySharedMethod () { ... } } }
Now we can add it to any component used by assigning it to the mixin config property. At runtime, Vue merges the component's properties with any added mixin.
// import MyMixin from "./"; export default { mixins: [MyMixin], data: () => ({ myLocalDataProperty: null }), methods: { myLocalMethod () { ... } } }
For this particular example, the component definition used at runtime should look like this:
export default { data: () => ({ mySharedDataProperty: null myLocalDataProperty: null }), methods: { mySharedMethod () { ... }, myLocalMethod () { ... } } }
Mixins are considered "harmful"
As early as mid-2016, Dan Abramov wrote "mixin Considered Harmful", in which he argued that using mixin to reuse logic in React components is an anti-pattern, advocating staying away from them.
Unfortunately, the shortcomings he mentioned about React mixins also apply to Vue. Before we understand how the Composition API overcomes these shortcomings, let's familiarize ourselves with them.
Naming conflict
We see how mixin mode merges two objects at runtime. What happens if both of them share the same name attribute?
const mixin = { data: () => ({ myProp: null }) } export default { mixins: [mixin], data: () => ({ // The same name! myProp: null }) }
This is where the merger strategy comes into play. This is a set of rules to determine what happens when a component contains multiple options with the same name.
The default (but configurable) merge policy for Vue components indicates that local options will override mixin options. The default (optional configuration) merge policy for Vue components indicates that the local options will override the mixin option. There are exceptions, however, for example, if we have multiple life cycle hooks of the same type, these hooks will be added to a hook array, and all hooks will be called in turn.
Although we should not encounter any actual errors, writing code becomes increasingly difficult when dealing with named properties across multiple components and mixins. Once third-party mixins are added as npm packages with their own named attributes, it will be particularly difficult because they can lead to conflicts.
Implicit dependency
There is no hierarchical relationship between mixin and the components that use it. This means that the component can use data properties defined in the mixin (e.g. mySharedDataProperty), but the mixin can also use data properties assumed to be defined in the component (e.g. myLocalDataProperty). This situation usually occurs when a mixin is used for shared input validation, which may expect a component to have an input value that will be used in its own validate method.
However, this may cause some problems. What happens if we want to refactor a component in the future and change the name of the variables required by mixin? When we look at this component, we will not find any problems. Linter will not find it either, we will only see errors at runtime.
Now imagine a component with many mixins. Can we reconstruct local data properties? Or will it break the mixin? We have to search manually to know.
Migrate from mixins
Alternatives to mixin include advanced components, utility methods and some other component composition patterns.
The downside of mixins is one of the main driving factors behind the Composition API, let's take a quick look at how it works and then see how it overcomes mixin issues.
Quick Start Composition API
The main idea of the Composition API is that we define them as JavaScript variables returned from the new setup function, rather than defining the component's functions (such as state, method, computed, etc.) as object properties.
Take this classic Vue 2 component as an example, which defines a "counter" function:
// export default { data: () => ({ count: 0 }), methods: { increment() { ++; } }, computed: { double () { return * 2; } } }
Here are the exact same components defined using the Composition API.
// import { ref, computed } from "vue"; export default { setup() { const count = ref(0); const double = computed(() => count * 2) function increment() { ++; } return { count, double, increment } } }
First of all, we will notice that we imported the ref function, which allows us to define a responsive variable that works almost the same as the data variable. The case of computed attributes is the same.
The increment method is not passive, so it can be declared as a normal JavaScript function. Note that we need to change the value of the subproperty count to change the responsive variable. This is because responsive variables created with ref must be objects in order to keep their responsiveness when passed.
After defining these functions, we will return them from the setup function. There is no difference in functionality between the two components above, all we do is use an alternative API.
Code Extraction
The first obvious advantage of the Composition API is that it is easy to extract logic.
Let's use the Composition API to refactor the components defined above so that the functionality we define is in the JavaScript module useCounter (the "use" before the feature description is a Composition API naming convention.).
// import { ref, computed } from "vue"; export default function () { const count = ref(0); const double = computed(() => count * 2) function increment() { ++; } return { count, double, increment } }
Code reuse
To use this function in a component, we simply import the module into the component file and call it (note that import is a function). This will return the variables we define, which we can then return from the setup function.
// import useCounter from "./"; export default { setup() { const { count, double, increment } = useCounter(); return { count, double, increment } } }
At first glance, this may seem a bit lengthy and meaningless, but let's see how this pattern overcomes the mixins problem discussed earlier.
Naming conflict resolved
We have learned before how mixins use the same attributes as those in the consumer component, or even more obscurely use the properties in other mixins used by the consumer component.
This is not a problem with the Composition API, as we need to explicitly name any state or method returned from the synthesis function.
export default { setup () { const { someVar1, someMethod1 } = useCompFunction1(); const { someVar2, someMethod2 } = useCompFunction2(); return { someVar1, someMethod1, someVar2, someMethod2 } } }
Naming conflicts are resolved in the same way as any other JavaScript variable.
Implicit dependency...solved!
I also saw earlier how mixin uses data attributes defined on the consumer component, which can make the code fragile and difficult to reason about.
Composition Function can also call local variables defined in the consuming component. The difference, however, is that this variable must now be passed explicitly to the synthesis function.
import useCompFunction from "./useCompFunction"; export default { setup () { // A synthesis function of a local value needs to be used const myLocalVal = ref(0); // It must be explicitly passed as a parameter const { ... } = useCompFunction(myLocalVal); } }
Summarize
The mixin mode looks safe on the surface. However, sharing code by merging objects becomes an anti-pattern because it adds vulnerability to the code and masks the ability of inference functions.
The clever part of the Composition API is that it allows Vue to rely on safeguards built into native JavaScript to share code, such as passing variables to functions and module systems.
Does this mean that the Composition API is superior to Vue's classic API in every way? No. In most cases, you have no problem sticking to the classic API. However, if you plan to reuse the code, the Composition API is undoubtedly superior.
This is the end of this article about how to replace Vue Mixins with the Vue3 Composition API. For more related Vue3 Composition Replace Vue Mixins, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!