目录

一、Mixin 源码分析

1. 核心源码位置

2. 源码实现

3. 合并策略源码(核心)

4. 不同选项的合并策略

二、合并策略总结

三、使用场景

1. 提取公共逻辑

2. 页面埋点统计

3. 列表页通用逻辑

4. 表单验证

四、面试回答模板

问题1:什么是 Vue Mixin?

问题2:Mixin 的合并策略是什么?

问题3:Mixin 的优缺点?

问题4:实际项目中如何使用 Mixin?

问题5:全局 Mixin 和局部 Mixin 的区别?

五、Vue 3 替代方案


一、Mixin 源码分析

1. 核心源码位置

Vue 2.x 中 mixin 的核心代码位于 src/core/global-api/mixin.js 和 src/core/util/options.js

2. 源码实现

// src/core/global-api/mixin.js
export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    // 将传入的 mixin 对象与 Vue.options 进行合并
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

3. 合并策略源码(核心)

// src/core/util/options.js
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  // ...
  const options = {}
  let key
  
  // 遍历父选项
  for (key in parent) {
    mergeField(key)
  }
  
  // 遍历子选项
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  
  function mergeField (key) {
    // 根据不同的选项使用不同的合并策略
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  
  return options
}

4. 不同选项的合并策略

// 生命周期钩子合并策略 - 合并成数组
strats.created = 
strats.mounted = 
strats.beforeMount = function (parentVal, childVal) {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)  // 都存在,合并成数组
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

// data 合并策略 - 递归合并,组件优先
strats.data = function (parentVal, childVal, vm) {
  return mergeDataOrFn(parentVal, childVal, vm)
}

// methods、props、computed 合并策略 - 组件覆盖 mixin
strats.methods =
strats.props =
strats.computed = function (parentVal, childVal) {
  if (!parentVal) return childVal
  const ret = Object.create(null)
  extend(ret, parentVal)
  if (childVal) extend(ret, childVal)  // 子组件会覆盖
  return ret
}

// watch 合并策略 - 合并成数组
strats.watch = function (parentVal, childVal) {
  // 合并成数组,都会执行
  if (!childVal) return Object.create(parentVal || null)
  if (!parentVal) return childVal
  const ret = {}
  extend(ret, parentVal)
  for (const key in childVal) {
    let parent = ret[key]
    const child = childVal[key]
    if (parent && !Array.isArray(parent)) {
      parent = [parent]
    }
    ret[key] = parent
      ? parent.concat(child)
      : Array.isArray(child) ? child : [child]
  }
  return ret
}

二、合并策略总结

选项类型 合并策略 执行顺序
生命周期钩子 合并成数组 mixin 先执行,组件后执行
data 递归合并对象 组件数据优先(覆盖 mixin)
methods/computed/props 对象合并 组件选项覆盖 mixin
watch 合并成数组 都会执行
components/directives/filters 对象合并 组件选项覆盖 mixin

三、使用场景

1. 提取公共逻辑

// 权限检查 mixin
export const permissionMixin = {
  methods: {
    hasPermission(permission) {
      return this.$store.getters.permissions.includes(permission)
    },
    checkPermission(permission) {
      if (!this.hasPermission(permission)) {
        this.$message.error('无权限访问')
        this.$router.push('/403')
      }
    }
  }
}

2. 页面埋点统计

export const trackMixin = {
  mounted() {
    // 页面访问埋点
    this.$track('page_view', {
      page: this.$route.path,
      title: document.title
    })
  },
  beforeDestroy() {
    // 页面离开埋点
    this.$track('page_leave', {
      page: this.$route.path,
      duration: Date.now() - this.enterTime
    })
  },
  data() {
    return {
      enterTime: Date.now()
    }
  }
}

3. 列表页通用逻辑

export const listMixin = {
  data() {
    return {
      list: [],
      loading: false,
      pagination: {
        current: 1,
        pageSize: 10,
        total: 0
      }
    }
  },
  methods: {
    async fetchList() {
      this.loading = true
      try {
        const { data, total } = await this.getListApi(this.pagination)
        this.list = data
        this.pagination.total = total
      } finally {
        this.loading = false
      }
    },
    handlePageChange(page) {
      this.pagination.current = page
      this.fetchList()
    }
  },
  mounted() {
    this.fetchList()
  }
}

4. 表单验证

export const formMixin = {
  data() {
    return {
      formLoading: false
    }
  },
  methods: {
    async submitForm(formName) {
      return new Promise((resolve, reject) => {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            resolve()
          } else {
            reject(new Error('表单验证失败'))
          }
        })
      })
    },
    resetForm(formName) {
      this.$refs[formName].resetFields()
    }
  }
}

四、面试回答模板

问题1:什么是 Vue Mixin?

回答:

Mixin 是 Vue 提供的一种代码复用机制,可以将组件的公共逻辑抽离出来。Mixin 对象可以包含任意组件选项(data、methods、生命周期等),当组件使用 mixin 时,mixin 的选项会被"混入"到组件自身的选项中。

问题2:Mixin 的合并策略是什么?

回答:

Vue 对不同类型的选项采用不同的合并策略:

  1. 生命周期钩子:合并成数组,mixin 的钩子先执行,组件的后执行
  2. data 数据:递归合并,组件数据优先,会覆盖 mixin 中同名属性
  3. methods/computed/props:对象合并,组件选项会覆盖 mixin
  4. watch:合并成数组,都会执行

源码中通过 mergeOptions 函数实现,针对不同选项类型使用 strats 策略对象中定义的合并函数。

问题3:Mixin 的优缺点?

回答:

优点:

  • 提高代码复用性,减少重复代码
  • 便于维护,公共逻辑集中管理
  • 灵活性高,可以混入多个 mixin

缺点:

  • 命名冲突:多个 mixin 可能有同名属性/方法
  • 依赖不明确:组件依赖的数据来源不清晰
  • 滥用导致维护困难:过多 mixin 会让代码难以追踪

现代替代方案:

  • Vue 3 推荐使用 Composition API(组合式 API)
  • 使用 composables 函数替代 mixin,依赖关系更清晰

问题4:实际项目中如何使用 Mixin?

回答:

在实际项目中,我主要在以下场景使用 mixin:

  1. 列表页通用逻辑:分页、加载、刷新等
  2. 权限控制:统一的权限检查方法
  3. 埋点统计:页面访问、用户行为追踪
  4. 表单处理:验证、提交、重置等通用方法

但现在新项目中,我更倾向使用 Vue 3 的 Composition API,通过自定义 hooks(composables)来实现代码复用,因为它更灵活、依赖更清晰。

问题5:全局 Mixin 和局部 Mixin 的区别?

回答:

// 全局 mixin - 影响所有组件
Vue.mixin({
  created() {
    console.log('全局 mixin')
  }
})

// 局部 mixin - 只影响当前组件
export default {
  mixins: [myMixin],
  // ...
}

全局 mixin 会影响所有 Vue 实例,包括第三方组件,应谨慎使用。通常只在插件开发或全局配置时使用。局部 mixin 只影响引入它的组件,更加安全可控。

五、Vue 3 替代方案

// 使用 Composition API 替代 mixin
import { ref, onMounted } from 'vue'

export function useList(api) {
  const list = ref([])
  const loading = ref(false)
  const pagination = ref({
    current: 1,
    pageSize: 10,
    total: 0
  })
  
  const fetchList = async () => {
    loading.value = true
    try {
      const { data, total } = await api(pagination.value)
      list.value = data
      pagination.value.total = total
    } finally {
      loading.value = false
    }
  }
  
  onMounted(() => {
    fetchList()
  })
  
  return {
    list,
    loading,
    pagination,
    fetchList
  }
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐