The Vue mixins
Many frameworks have mixins design patterns, and VUe2 is no exception. When multiple components want to reuse some functionality, the component can use a mixin object, where all mixin options are blended into the component’s own options (a mixin object can contain any componentOptions of type componentOptions).
Mixins objects:
// ./mixins/module1.js
export default {
data() {
return {
clientX: 0}; },created() {
window.addEventListener('mousemove'.(e) = > {
this.clientX = e.clientX;
});
this.$once('hook:beforeDestoryed'.() = > {
window.removeEventListener('mousemove'); }); }};Copy the code
Component A to be mixed in
import module1 from '@/mixins/module1';
export default {
name: 'A'.mixins: [module1],
render(h) {
return <div>{this.clientX}</div>; }};Copy the code
Component B to be mixed in
import module1 from '@/mixins/module1';
export default {
name: 'B'.mixins: [module1],
render(h) {
return <span>{this.clientX}</span>; }};Copy the code
Above, components A and B get the clienX of the mouse as they move the mouse, and the mixins property allows us to pull out the reused logic and make it look flexible.
The disadvantage of a mixin
- Unclear source
- Namespace conflict
- Type inference difficulty
Unclear source
The above component A does not declare clientX, so we can infer that it should come from Module1 in mixins, because there is only Module1 in mixins. But does the following code infer which mixin clientX and clientY came from when A component refers to multiple mixins? Obviously not, you have to jump to the declaration of the file. Mixins can be seen to have a problem with unclear origins.
Component A to be mixed in
import module1 from '@/mixins/module1';
import module2 from '@/mixins/module2';
export default {
name: 'A'.mixins: [module1, module2],
render(h) {
return <div>{this.clientX} {this.clientY}</div>;
// clientX from module1 or moudle2?
// Is clientY from module1 or moudle2?}};Copy the code
Namespace conflict
When components and mixins have options with the same name, those options are “merged” in the appropriate way. For example, data objects are internally recursively merged and component data takes precedence in the event of a conflict.
The official VUE documentation clearly states that a merge occurs when an attribute with the same name appears. For example, the data component declares that clientX overrides properties of the same name in mixins (lifecycle functions are appended to their respective arrays and only superimposed without worrying about overrides). This convention may not be confusing to developers, but always takes precedence.
import module1 from '@/mixins/module1';
import module2 from '@/mixins/module2';
export default {
name: 'A'.mixins: [module1, module2],
data() {
return {
clientX: 0,}}render(h) {
return <div>{this.clientX} {this.clientY}</div>;
// clientX comes from its own data and overrides mixins
// Is clientY from module1 or moudle2?}};Copy the code
When I want to add A mixin to A component, the biggest puzzle for me is how do I name properties and methods? Since the same name is merged, I had to make sure that each property or method to be mixed in was unique, which led me to look at the @/mixins/module1 and @/mixins/ Module2 code logic one by one to prevent naming conflicts, which added to the developer’s burden.
Type inference difficulty
When we use ts to do type inference for our code, in vscode we can easily see the type of the variable by hovering over it if we already have type restrictions.
When using mixins, it is difficult to infer clientX because the source of clientX’s declaration is not known, and vscode normally only treats it as any. In the days of TS, any will be beaten.
An alternative to mixins
As the main problem of mixins is unclear source and namespace conflict when mixing multiple component parameters, the community has the following two hack schemes that can be used as substitutes for mixins to some extent.
- Hoc high-level components
- Slot components
Hoc high-level components
Higher-order components in VUE can be thought of as a function whose input parameter is a component that returns a completely new component.
hoc = f(componentOptions) => component
Mixins scheme
// ./mixins/module1.js
export default {
data() {
return {
count: 0}; },methods: {
addCount() {
this.count++; }}};// A.vue
import module1 from '@/mixins/module1';
export default {
name: "A".mixins: [module1],
render(h) {
return <div @click="this.addCount">{this.count}</div>; }};Copy the code
The hoc scheme withModule passes count and addCount to Comp. At the same time, the A component should be transformed accordingly.
// ./hoc/withModule1.js
export function withModule1(Comp) {
return {
data() {
return {
count: 0}; },render(h) {
return <Comp count={this.count} addCount={this.addCount}></Comp>
},
methods: {
addCount() {
this.count++; }}}},// A.vue
import withModule1 from '@/hoc/withModule1';
export default withModule1({
props: ['count'.'addCount'].name: "A".render(h) {
return <div @click="this.addCount">{this.count}</div>; }});Copy the code
Component A is transformed into props to receive count and addCount, which can be directly derived from withModule1.
This seems to be a good method, but in fact it does not solve the problem. Hoc receives arbitrary components, which naturally can not avoid the following situation.
// A.vue
import withModule1 from '@/hoc/withModule1';
import withModule2 from '@/hoc/withModule2';
import withModule3 from '@/hoc/withModule3';
export default withModule3(withModule2(withModule1({
props: ['count'.'addCount'].name: "A".render(h) {
return <div @click="this.addCount">{this.count}</div>; }})));Copy the code
Nested scenarios, can you still see the source of the data? Suddenly, hoc is back to its roots as a replacement for mixins, rather than solving the problem of unclear origin and namespace conflicts.
Slot components
Slot component scheme
// ./slot/slotModule1.js
export default {
data() {
return {
count: 0}; },render(h) {
const vNodes = this.$scopedSlots.default
? this.$scopedSlots.default({
count: this.count,
addCount: this.addCount,
})
: null;
if (vNodes.length > 1) { // Multiple nodes
return <div>{vNodes}</div>;
}
return vNodes;
},
methods: {
addCount() {
this.count++; ,}}};// A.vue
export default {
name: "A".props: ['count'.'addCount'].render(h) {
return <div @click="this.addCount">{this.count}</div>; }};Use A in the parent component
<template>
<SlotModule1 v-slot="{ count, addCount }">
<A :count="count" :addCount="addCount"></A>
</SlotModule1>
</teamplate>
import SlotModule1 from '@/slot/slotModule1.js';
import A from '@/view/A';
export default {
name: 'AParent'.component: {
SlotModule1,
A
}
}
Copy the code
The scheme of the Slot component makes it clear what data is being passed down, even if there are multiple layers of nesting. It can be said that slot solution solves some of the pain points of mixins, and is the most mature solution to replace mixins. However, slot solution is relatively invasive to the code and requires varying degrees of modification. Multistage nested
Use A in the parent component
<template>
<SlotModule1 v-slot="{ count, addCount }">
<SlotModule2 v-slot="{ clientX }">
<A :count="count" :addCount="addCount" />
<B :clientX="clientX" />
</SlotModule2>
</SlotModule1>
</teamplate>
import SlotModule1 from '@/slot/slotModule1.js';
import SlotModule2 from '@/slot/slotModule2.js';
import A from '@/view/A';
import B from '@/view/B';
export default {
name: 'AParent'.component: {
SlotModule1,
SlotModule2,
A,
B
}
}
Copy the code
conclusion
For the most part, Vue2 mixins are still very useful, and some extreme scenarios can be slotted. Vue3’s composition-API has already solved the problem of code reuse and logic hopping. If you are still using VUE2 and want to use composition-API, Vue has long introduced transition schemes that allow you to use ref, defineComponent, watchEffect, etc. It has been tested in the production project and can be safely eaten.
Portal: github.com/vuejs/compo…