← 返回文章列表
Vue·826 次阅读

Vue 2 逻辑复用两板斧:mixin 与自定义指令

年底做项目复盘,统计了一下代码库:mixin 用了 23 处,自定义指令 11 个。有的用得很值,有的现在看着想抽自己。趁着记忆还新鲜,把这两个 Vue 2 时代的复用方案整理一下。

mixin:组件选项的"合并补丁"

mixin 的本质是把组件选项(data、methods、生命周期、computed)抽出来共享:

// mixins/tablePage.js
export default {
  data() {
    return {
      loading: false,
      page: 1,
      pageSize: 20,
      total: 0,
      list: [],
    }
  },
  methods: {
    async fetchList() {
      this.loading = true
      try {
        const { records, total } = await this.api({
          page: this.page,
          pageSize: this.pageSize,
        })
        this.list = records
        this.total = total
      } finally {
        this.loading = false
      }
    },
    handlePageChange(page) {
      this.page = page
      this.fetchList()
    },
  },
  created() {
    this.fetchList()
  },
}

用的时候混入,组件只需提供差异部分:

import tablePage from '@/mixins/tablePage'

export default {
  mixins: [tablePage],
  data() {
    return {
      api: fetchUserList, // 每个页面换掉请求函数即可
    }
  },
}

合并规则要记牢:数据和方法冲突时组件优先,生命周期钩子都会执行(mixin 的先跑)。

mixin 的三宗罪

用得多了,问题就暴露了:

  1. 来源不明:组件里突然出现个 this.fetchList,新人得翻半天才知道来自哪个 mixin
  2. 命名冲突:两个 mixin 都有 page 字段,后面混入的静默覆盖前面的,bug 极难查
  3. 隐式依赖:mixin A 的方法里用了 this.xxx,而这个 xxx 是 mixin B 提供的——耦合藏得深不见底

我的教训:mixin 最多混两层,每个 mixin 必须自足(不假设外部提供任何字段)。跨项目的通用逻辑宁可复制粘贴,也别搞 mixin 套 mixin。

自定义指令:DOM 操作的封装出口

Vue 哲学是"数据驱动,别碰 DOM",但总有些事必须碰:聚焦、点击外部关闭、权限控制、复制到剪贴板。这些就该指令出场了。

最常用:v-focus

Vue.directive('focus', {
  inserted(el) {
    el.focus()
  },
})
<input v-focus placeholder="打开弹窗自动聚焦" />

实战级:点击外部关闭

做下拉菜单必备:

Vue.directive('click-outside', {
  bind(el, binding) {
    el._clickOutsideHandler = (event) => {
      // 点击发生在元素外部才触发回调
      if (!el.contains(event.target)) {
        binding.value(event)
      }
    }
    document.addEventListener('click', el._clickOutsideHandler)
  },
  unbind(el) {
    document.removeEventListener('click', el._clickOutsideHandler)
  },
})
<div v-click-outside="closeDropdown" class="dropdown">
  下拉内容,点外面就调 closeDropdown
</div>

注意 unbind 里必须移除监听,否则内存泄漏。handler 挂在 el 上是为了在 unbind 时能拿到同一个函数引用。

权限指令:v-permission

后台系统高频需求——没权限的按钮直接不渲染:

import store from '@/store'

Vue.directive('permission', {
  inserted(el, binding) {
    const permissions = store.state.user.permissions
    if (!permissions.includes(binding.value)) {
      el.parentNode.removeChild(el)
    }
  },
})
<button v-permission="'user:delete'">删除用户</button>

指令的钩子函数

完整钩子有五个,日常 90% 只用前两个:

钩子 时机
bind 指令第一次绑定到元素(只一次,适合初始化)
inserted 元素插入父节点时(操作 DOM 用它)
update 所在组件 VNode 更新时(值可能变了)
componentUpdated 组件及其子组件全部更新完
unbind 指令解绑(清理工作必须在这做)

参数方面:binding.value 是指令的值,binding.arg 是参数(v-x:arg),binding.modifiers 是修饰符对象(v-x.a.b → { a: true, b: true })。

怎么选

  • 复用的是组件状态和行为(数据、方法、生命周期)→ mixin
  • 复用的是DOM 层面的交互(聚焦、点击外部、拖拽)→ 指令
  • 两者都嫌重的时候,想想能不能抽成普通工具函数——很多时候答案是可以

小结

  • mixin 合并组件选项,注意冲突规则和"最多两层"的自我约束
  • 指令封装 DOM 操作,inserted 干活、unbind 清理
  • v-permission、v-click-outside、v-focus 是后台三件套,几乎每个项目都会写一遍
  • Vue 3 里 mixin 的地位被 Composition API 取代,但指令依然活跃在 DOM 操作一线

明年的技术趋势里都在传 Vue 3 的 Composition API RFC,看设计确实是从 mixin 的痛点出发的,等正式发布后再写篇对比。

看到这里了?如果这篇文章帮到过你

请我喝瓶可乐就好 🥤

收款码

评论0 条

还没有评论,来抢沙发~

发表评论

评论需审核后显示

© 2026 许冬博客 · 专注 JS 技术分享

·RSS 订阅·Powered by Nuxt 4