【Vue】如何避免mixin导致的混乱
2023/07/03 11:10:09
mixin 会带来什么问题
Vue2 中可以使用 mixin 来封装可复用的逻辑,它可以向一个对象注入另一个对象的所有属性,这让它非常灵活,但是同时也带来了一些问题:
- 来源不明:使用的 mixin 越多,越难找到某个属性或方法来自哪里。
- 无法精确引入:引入一个 mixin 时,会引入这个 mixin 的全部属性,因此可能会引入一些用不到的东西,也很难清楚知道到底引入了什么内容。
- 命名冲突:因为是将两个对象合并,所以可能会遇到命名冲突的问题。
如何解决上述的问题
可以转变 mixin 引入的方式,传统的 mixin 都是直接引入一个对象,我们可以封装一个函数来生成需要引入的 mixin 对象,通过参数来控制要引入的变量名、方法名。
示例一:鼠标跟踪器
实现一个鼠标跟踪功能,获取鼠标当前的 x、y 坐标,并将这个功能封装到 mixin 中。
先来看看期望中引入 mixin 的方式:
<template>
<div>{{ posX }}-{{ posY }}</div>
</template>
<script>
import { mousePosition } from "./mixin/mouse";
export default {
mixins: [
mousePosition({
x: "posX",
y: "posY",
}),
],
};
</script>
这里引入了一个 mousePosition 的方法,这个方法的返回值是一个 mixin 对象,通过参数告诉 mousePosition 方法用 posX
来接收 x
、用 posY
来接收 y
。
mousePosition
的实现如下:
const defaultOption = {
x: "x",
y: "y",
};
export function mousePosition(option = defaultOption) {
const xKey = option.x;
const yKey = option.y;
const data = {
[xKey]: 0,
[yKey]: 0,
};
document.addEventListener("mousemove", (event) => {
data[xKey] = event.clientX;
data[yKey] = event.clientY;
});
return {
data() {
return data;
},
};
}
示例二:公共表单逻辑
有这样一个业务场景:一个表单需要展现不同的样式,这种场景下可以将表单的逻辑部分封装到 mixin 中,只在组件中重写样式即可。
先看看引入方式:
<template>
<div>
<div>
<span>姓名:</span>
<input type="text" v-model="customerName" />
</div>
<div>
<span>电话:</span>
<input type="text" v-model="customerPhone" />
</div>
<button @click="onSubmit">提交</button>
</div>
</template>
<script>
import { formCore } from "./mixin/form";
export default {
mixins: [
formCore({
name: "customerName",
phone: "customerPhone",
onSubmit: "onSubmit",
}),
],
};
</script>
<style scoped>
@import "@/styles/components/formV1.css";
</style>
对于不同的组件,只需要重写 template 和 style 即可。
mousePosition
的实现如下:
const defaultOption = {
name: "name",
phone: "phone",
onSubmit: "onSubmit",
};
export function formCore(option = defaultOption) {
const nameKey = option.name;
const phoneKey = option.phone;
const onSubmitKey = option.onSubmit;
const data = {
[nameKey]: "",
[phoneKey]: "",
};
const methods = {};
if (onSubmitKey) {
methods[onSubmitKey] = function () {
const valid = () => {
if (!this[nameKey]) {
alert("请输入姓名");
return false;
}
if (!this[phoneKey]) {
alert("请输入电话");
return false;
}
return true;
};
const validResult = valid();
if (validResult) {
alert(`${this[nameKey]}-${this[phoneKey]}`);
}
};
}
return {
data() {
return data;
},
methods,
};
}
通用倒计时逻辑
代码来源:我可能发现了 Vue Mixin 的正确用法——动态 Mixin
// 通用倒计时逻辑
export function useCountdown(getterKey, setterKey) {
if (typeof getterKey === "string") {
const mixin = {
data() {
return {
// 属性注入到 data 中
[getterKey]: 0,
};
},
created() {
let timer = null;
this.$watch(
getterKey,
(val, oldVal) => {
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
// 非数字类型转成数字类型
if (typeof val !== "number") {
this[getterKey] = Number(val);
return;
}
// 负数,转成 0
if (val < 0) {
this[getterKey] = 0;
return;
}
// 小数,向上取整
if (val % 1 !== 0) {
this[getterKey] = Math.ceil(val);
return;
}
if (val > 0) {
timer = setTimeout(
() => {
this[getterKey] = val - 1;
},
// 整数时倒计时 1s,小数时倒计时超出整数秒的时间
oldVal % 1 > 0 ? (oldVal * 1000) % 1000 : 1000
);
}
},
{
immediate: true,
}
);
// 组件销毁时清除定时器
this.$on("hook:destroyed", () => {
if (timer !== null) {
clearTimeout(timer);
}
});
},
};
if (typeof setterKey === "string") {
// 再注入一个方法
mixin.methods = {
[setterKey](val) {
this[getterKey] = val;
},
};
}
return mixin;
}
// 参数不规范时抛出错误
throw new Error("Invalid arguments");
}