【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 的正确用法——动态 Mixinopen in new window

// 通用倒计时逻辑
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");
}

参考

我可能发现了 Vue Mixin 的正确用法——动态 Mixinopen in new window