likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 29

我正在参加「掘金·启航计划」

令人头疼的 mixin

众所周知,vue 的 mixins 是个非常灵活,但又很容易带来混乱的 API。不知道你是否像我一样,常常因为项目中用了 mixins 而感到头疼。比如说:

  • 来回切换文件:有人会因为文件太大而把一部分内容提取到单独的 mixin 中,看起来文件变小了,但改代码的时候需要在多个文件之间不停切换
  • 找不到函数和变量:模板中用到一个函数,在 methods 中搜索不到,然后发现组件引入了很多 mixins,不知道要该去哪里找

Mixins 本该是一种强大的重用代码的手段,但使用之后往往带来更多的混乱,代码变得不易维护。所以,在 Vue3 中,不再推荐使用 mixins,而是改用 组合式 API 的方式来重用代码。

那么,我们是不是不应该在日常代码中使用 mixins 呢?我觉得也不是。Mixins 是个强大的工具,但本身存在一些严重的问题,如果我们能把 mixins 中存在的问题解决掉,那剩下的就会是:强大。

为什么会这样?

面对 mixins 这样灵活的工具,我们必须理解它的问题所在,才能避免在使用时带来混乱。

本质上,mixins 是一种重用代码的手段,它可以向一个对象中注入另一个对象中的所有属性。并且,一个对象可以同时从多个不同对象中注入属性,所以非常灵活。而这样的灵活导致了一些致命的问题:

  1. 来源不明:使用的 mixins 越多,越难找到某个属性或方法来自哪里
  2. 无法精确引入:引入一个 mixin 时,会引入这个 mixin 的全部属性,因此很可能会引入一些用不到的东西,也会导致很难清楚知道到底引入了哪些属性和方法
  3. 命名冲突:引入多个 mixins 时,引入的属性名可能会冲突
  4. 耦合问题:不规范地使用 mixins 功能,可能会导致耦合问题,比如因为组件太大而抽离一部分属性到 mixins 中,这样产生的 mixins 一般会互相依赖,造成强耦合

作为对比,我们看看其他类似技术为什么没有这些问题:

  • ES模块:一个模块可以导出很多东西,但我们可以选择性导入,这样就能清楚知道导入了哪些内容,并且导入的变量和函数可以重命名,这样就解决了命名冲突的问题
  • Vue3组合式函数:Vue3 推荐使用组合式函数来替代 mixins 实现逻辑重用,组合式函数一般返回一个对象,解构时可以只取出对象中的部分属性,从而可以精确控制引入内容,并且解构语法支持自定义变量名,因此不会导致命名冲突

如果我们想要放心使用 mixins 功能,就必须解决以上问题。从 ES模块Vue3组合式函数 的设计,我们可以看出一些关键点,那就是需要能 精确控制引入的内容,具体包括:

  • 清晰看到引入的内容:在引入 mixins 的地方,可以直接看到引入了哪些属性和方法
  • 支持部分引入:可以只引入需要用到的属性,而不是引入 mixin 对象上的全部属性
  • 自定义引入的属性名:可以指定要引入的属性和方法的名字,避免命名冲突

这看起来似乎是不可能的,因为 mixin 就是一个对象,一旦引入就要接受这个对象上的所有属性和方法,也不可能在引入的时候去修改对象的属性名。

那还能不能用?

当然,你可以只使用生命周期钩子的功能,而不引入 data、计算属性和方法,这样就不会往实例上添加额外属性,也就不会有这些问题。但是,这会使 mixins 的功能大打折扣。

幸运的是,确实有一个方法能够精确控制引入的内容,那就是动态创建 mixin 对象。

一般来说,我们习惯把 mixin 定义为一个对象,然后在组件中直接引入。但其实只要把它定义成一个函数,问题就会得到解决。

我们可以把 mixin 定义成一个函数,通过函数参数接收需要引入的属性名,然后生成并返回真正的 mixin 对象。这样的 mixin 就是动态生成的。引入时,调用这个函数,通过传入的参数就可以精确控制引入的内容,可以指定要引入哪些属性,也可以指定引入的属性名字。

import mouse from '@/mixins/mouse'
export default {
  mixins: [
    mouse({
      mouseX: 'x',
      mouseY: 'y'
    })
  ]
}

我们给这种方法取一个不冲突的名字,就称为 动态Mixin 好了。

当然,这种写法增加了编写 mixins 的难度,所以我认为,mixins 适合用来引入一些简单的属性。比如,VueUse 中的那些工具方法就很适合使用动态 Mixin 来实现。

动态Mixin示例

接下来我列举一些自己在项目中经常使用的 mixins,大家可以体会一下这种动态 Mixin 的适用场景以及带来的好处。

倒计时

倒计时是业务中经常会用到的一种场景,比如发送验证码时一般会显示倒计时。然后有些使用须知的弹窗,确认按钮也会在倒计时结束后才允许点击。

这种倒计时非常简单,只需要一个记录当前剩余时间的属性,加上一个设置倒计时时长的函数。这种场景就非常适合使用 mixins 来实现。实现过程先不考虑,使用效果是这样的:

<template>
  <button :disabled="!!seconds" @click="setCountdown(60)">
    {{ seconds || '发送验证码' }}
  </button>
</template>
<script>
import countdown from '@/mixins/countdown';
export default {
  // 引入 seconds 属性存储剩余秒数
  // 引入 setCountdown 方法设置倒计时秒数
  mixins: [countdown('seconds', 'setCountdown')],
};
</script>

这个 mixins 引入了一个属性和一个方法,引入的内容都是可以指定名字的。调用 setCountdown 方法时会设置 seconds 属性的值,并重新开始倒计时。

不过在 Vue 中,我们可以充分利用 Vue 自带的响应式特性。我们可以利用 watch 去进行监听,在检测到 seconds 变化时,重新开始倒计时。这样就可以少引入一个方法。

<template>
  <button :disabled="!!seconds" @click="seconds = 60">
    {{ seconds || '发送验证码' }}
  </button>
</template>
<script>
import countdown from '@/mixins/countdown';
export default {
  // 引入 seconds 属性记录倒计时秒数
  mixins: [countdown('seconds')],
};
</script>

看,使用 mixins 之后,代码变得特别简洁清晰。而且只需要写一次,今后就可以在其他项目中直接使用这个 mixin。而如果不使用 mixins 自己实现,就需要多写很多代码,完善的写法每次还需要考虑怎么清除定时器,防止内存泄漏。

这个 mixin 的具体实现可以看这里:github.com/web1706/vue…。你可以直接在你的项目中使用。

路由query参数

编写一个操作路由 query 参数的 mixin 其实是挺有必要的。

想象这样一个场景:页面上有一个 Tabs 组件,有多个页签。当我们切换页签时,一般会把当前激活的页签的值存储在 data 中。

export default {
  data() {
    return {
      // 当前 tab 页
      currentTab: 'one'
    }
  }
}

这样其实会有一个问题,就是刷新页面后,或者进入其他页面再返回时,是回不到之前的页签的。如果有一天需要改成刷新时保留激活的页签,最好的方法就是把页签值存入路由 query 中。这时如果有这样一个操作 query 参数的 mixin 就方便了。

import routeQuery from '@/mixins/route-query';
export default {
  mixins: [
    // 当前 tab 页
    routeQuery('currentTab'),
  ],
};

这个 mixin 会从 this.$route.query 中读取对应的参数值,放到名为 currentTab 的属性中。并且这个属性是可写的!写入时会自动调用 this.$router.replace 去替换 query 中的参数值。

当然,你可能还需要指定默认值,这也没问题,只需要改成对象形式:

import routeQuery from '@/mixins/route-query';
export default {
  mixins: [
    routeQuery({
      // 当前 tab 页
      currentTab: {
        default: 'one'
      }
    }),
  ],
};

或许,你想在 query 中使用一个不一样的参数名。

import routeQuery from '@/mixins/route-query';
export default {
  mixins: [
    routeQuery({
      // 当前 tab 页
      currentTab: {
        from: 'tab'
      },
      // 简写形式
      activeTab: 'tab'
    }),
  ],
};

想要使用数字类型也没问题。

export default {
  mixins: [
    routeQuery({
      // 对引入的属性值进行转换
      companyId: {
        type: Number,
      },
      // 简写形式
      deptId: Number,
    }),
  ],
};

可以看到,这个 mixin 非常灵活,功能也比传统 mixins 强大很多。

这个 mixin 的具体实现可以看这里:github.com/web1706/vue…。同样,你也可以把它用在你的项目中。

后记

本文介绍的这种使用函数动态生成 Mixin 的方式,大家觉得怎么样呢?大家平时又是如何看待和使用 Mixin 的呢?快来交流一下各自的想法吧。

转载自:https://juejin.cn/post/7244070072787763256
评论
请登录