Skip to content

VueJS - Mixins

VueJS - 逻辑复用:Mixins 与 Composables

Section titled “VueJS - 逻辑复用:Mixins 与 Composables”

代码复用对于可维护的应用程序至关重要。Vue 提供了几种在组件之间共享逻辑的方式。在历史上,Mixins 是 Vue 2 中常见的做法。然而,Vue 3 的 Composition API 提供了一种更灵活、更明确的模式,称为 Composables。

Mixins 是包含组件选项(如 data、methods、created 等)的对象。当一个组件使用 mixin 时,mixin 的选项会被“混合”到组件自身的选项中。

让我们定义一个简单的 mixin,它在组件创建时记录一条消息:

// loggerMixin.js
export const loggerMixin = {
created() {
console.log(`Component ${this.$options.name || 'unnamed'} created via mixin.`);
if (this.logMessage) { // 访问组件中可能定义的数据属性
this.logMessage('Mixin logging activated!');
}
},
methods: {
alertMixinMessage(msg = 'Hello from mixin!') {
alert(msg);
}
}
};

现在,让我们在一个组件中使用这个 mixin(使用 Vue 2 风格的 Options API 进行演示):

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue Mixin Example</title>
<script src="https://unpkg.com/vue@2"></script> <!-- 使用 Vue 2 进行 mixin 演示 -->
</head>
<body>
<div id="app">
<my-component></my-component>
</div>
<script type="module">
// 假设 loggerMixin 从 './loggerMixin.js' 导入
const loggerMixin = {
created() {
console.log(`Component ${this.$options.name || 'unnamed'} created via mixin.`);
if (this.logMessage) {
this.logMessage('Mixin logging activated!');
}
},
methods: {
alertMixinMessage(msg = 'Hello from mixin!') {
alert(msg);
}
}
};
const MyComponent = {
name: 'MyComponent',
mixins: [loggerMixin], // 使用 mixin
data() {
return {
componentData: 'Some data specific to the component'
};
},
methods: {
logMessage(msg) { // 在组件中定义的方法
console.log(`Component Log: ${msg}`);
},
callMixinMethod() {
this.alertMixinMessage('Called from component!'); // 可以调用 mixin 的方法
}
},
template: `<div>
<p>{{ componentData }}</p>
<button @click="callMixinMethod">Test Mixin Method</button>
</div>`,
mounted() {
console.log('Component mounted.');
}
};
new Vue({
el: '#app',
components: { MyComponent }
});
</script>
</body>
</html>

当这段代码运行时:

  • loggerMixin 中的 created 钩子运行。
  • 组件的 mounted 钩子运行。
  • 组件可以访问它自己的 data (componentData)。
  • 组件可以调用在其自身内部定义的方法 (logMessage)。
  • 组件也可以调用在 mixin 中定义的方法 (alertMixinMessage)。
  • 如果存在,mixin 可以潜在地调用在组件中定义的方法(例如这里的 logMessage)。

预期的控制台输出:

  1. Component Log: Mixin logging activated! (Mixin 调用组件的方法)
  2. Component MyComponent created via mixin. (Mixin 的 created 钩子)
  3. Component mounted. (组件的 mounted 钩子)

点击按钮将触发一个 alert: Called from component!

当组件及其 mixin(s) 定义了重叠的选项时,Vue 会根据特定的策略合并它们:

  • data: 递归合并。在根级别发生冲突时,组件的数据优先级更高。
  • 生命周期钩子(例如 created、mounted):合并到一个数组中。所有钩子(mixin 优先,然后组件)都将被调用。
  • 像 methods、components、directives 等选项:合并到同一个对象中。如果键冲突,组件的选项优先级更高。
const conflictMixin = {
data() {
return { message: 'Mixin Message', commonValue: 1 };
},
methods: {
greet() { console.log('Hello from Mixin'); },
commonMethod() { console.log('Common method from Mixin'); }
},
created() { console.log('Mixin created hook'); }
};
const ConflictingComponent = {
mixins: [conflictMixin],
data() {
return { message: 'Component Message', specificValue: 2 }; // 'message' 冲突
},
methods: {
farewell() { console.log('Goodbye from Component'); },
commonMethod() { console.log('Common method from Component'); } // 'commonMethod' 冲突
},
created() { console.log('Component created hook'); },
mounted() {
console.log(this.message); // 输出:Component Message (组件数据获胜)
console.log(this.commonValue); // 输出:1 (来自 mixin,无冲突)
console.log(this.specificValue); // 输出:2 (来自组件)
this.greet(); // 输出:Hello from Mixin
this.farewell(); // 输出:Goodbye from Component
this.commonMethod(); // 输出:Common method from Component (组件方法获胜)
},
template: '<div>Conflicting Component</div>'
};
// 在 Vue 实例中:
// new Vue({ el: '#app', components: { ConflictingComponent }, template: '<conflicting-component />' });

预期的控制台输出(在挂载时):

  1. Mixin created hook
  2. Component created hook
  3. Component Message
  4. 1
  5. 2
  6. Hello from Mixin
  7. Goodbye from Component
  8. Common method from Component

虽然 mixins 允许代码复用,但它们有显著的缺点,尤其是在大型应用程序中:

  • 隐式依赖 (Implicit Dependencies): 如果使用多个 mixins,组件内部使用的属性(如 this.message)或方法实际上来自哪里并不清楚。
  • 命名空间冲突 (Namespace Collisions): 正如所见,来自不同 mixins 或组件本身的属性和方法名称可能会冲突,导致意外行为或需要仔细的命名约定。
  • 有限的复用性/配置 (Limited Reusability/Configuration): Mixins 在使用时不容易通过参数进行配置。向 mixin 传递数据通常依赖于组件中存在特定的 data 属性。
  • TypeScript 支持 (TypeScript Support): 使用 mixins 进行类型推断可能很复杂且不太可靠。

Vue 3 的 Composition API 引入了“Composable 函数”(或称“Composables”)作为封装和复用状态逻辑的主要模式。Composable 只是一个函数,它利用 Vue 的 Composition API(如 ref、computed、watch、生命周期钩子)来管理状态和逻辑。

Composables 通过以下方式解决了 mixins 的缺点:

  • 显式来源 (Explicit Source): Composable 函数返回的值必须在组件中被显式解构或赋值,使其来源清晰。
  • 无命名空间冲突 (No Namespace Collisions): 返回的值可以在组件内部按需重命名。
  • 灵活的参数 (Flexible Arguments): Composables 只是函数,因此它们可以接受参数进行定制。
  • 更好的类型推断 (Better Type Inference): TypeScript 与 Composables 的配合更加可靠。

让我们创建一个跟踪鼠标位置的 Composable:

useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue';
export function useMousePosition() {
// 由 Composable 封装和管理的状态
const x = ref(0);
const y = ref(0);
// 由 Composable 封装的逻辑
function update(event) {
x.value = event.pageX;
y.value = event.pageY;
}
// 由 Composable 管理的生命周期钩子
onMounted(() => window.addEventListener('mousemove', update));
onUnmounted(() => window.removeEventListener('mousemove', update));
// 将管理的状态作为返回值暴露
return { x, y };
}

现在,让我们在一个使用 <script setup> 的 Vue 3 组件中使用这个 Composable:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue Composable Example</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
Mouse position: x={{ mouseX }}, y={{ mouseY }}
</div>
<script type="module">
import { createApp, ref, onMounted, onUnmounted } from 'vue';
// 假设 useMousePosition 从 './useMousePosition.js' 导入
function useMousePosition() {
const x = ref(0);
const y = ref(0);
function update(event) {
x.value = event.pageX;
y.value = event.pageY;
}
onMounted(() => window.addEventListener('mousemove', update));
onUnmounted(() => window.removeEventListener('mousemove', update));
return { x, y };
}
createApp({
setup() {
// 使用 Composable
const { x, y } = useMousePosition();
// 如果需要,可以重命名以避免冲突
// const { x: mouseX, y: mouseY } = useMousePosition();
// 返回将在模板中使用的状态
// 在这里使用不同的名称以便模板更清晰
return {
mouseX: x,
mouseY: y
};
}
}).mount('#app');
</script>
</body>
</html>

在此 Vue 3 示例中:

  • 我们在 setup() 函数内部导入并调用了 useMousePosition()。
  • 我们解构了返回的 x 和 y refs。
  • x 和 y 的来源明确是 useMousePosition Composable。
  • 添加/移除事件监听器的逻辑被封装在 Composable 内部。
  • 组件只需要消费 Composable 提供的状态。

虽然 mixins 仍然存在,并且你可能会在旧的 Vue 2 代码库中遇到它们,但 Composables 是现代 Vue 3 开发中推荐的代码复用模式。与 mixins 相比,它们提供了卓越的显式性、灵活性、冲突避免和 TypeScript 支持。

如果从 Vue 2 迁移,将 mixins 重构为 Composables 通常是一个值得付出的努力,可以提高可维护性。

进一步阅读:Vue 官方文档关于 Composables 的介绍:https://vuejs.org/guide/reusability/composables.html