在 Vue 开发中,你是否使用过:
            
            
              js
              
              
            
          
          import BaseComponent from './base';
import { formMixin } from './mixins';
export default {
  extends: BaseComponent,
  mixins: [formMixin, authMixin],
  // ...
}
        "当
mixin和extends冲突时,谁的优先级更高?" "生命周期钩子的执行顺序是怎样的?" "data函数如何合并?"
本文将从 源码级 mergeOptions 到 实战合并规则,彻底解析 Vue 组件选项的合并逻辑。
一、核心概念对比
| 特性 | mixins | 
extends | 
|---|---|---|
| 类型 | 数组(多个混入) | 单个对象或组件 | 
| 用途 | 横向复用逻辑(如表单、权限) | 纵向继承(扩展基类组件) | 
| 合并顺序 | 从前到后依次合并 | 先合并 extends | 
| 优先级 | 组件自身 > mixins > extends | 组件自身 > mixins > extends | 
✅ 最终优先级:组件选项 > mixins > extends
二、源码级合并流程:mergeOptions
🔄 执行顺序(关键!)
            
            
              js
              
              
            
          
          function mergeOptions(parent, child, vm) {
  // 1. 规范化选项(props/inject/directives)
  normalizeProps(child, vm);
  normalizeInject(child, vm);
  normalizeDirectives(child, vm);
  // 2. 先处理 extends
  if (child.extends) {
    parent = mergeOptions(parent, child.extends, vm);
  }
  // 3. 再处理 mixins
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      const mixin = child.mixins[i];
      parent = mergeOptions(parent, mixin, vm);
    }
  }
  // 4. 最后合并当前组件选项
  const options = {};
  for (const key in parent) {
    mergeField(key);
  }
  for (const key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key);
    }
  }
  function mergeField(key) {
    // 根据 key 调用不同的合并策略
    const strat = strats[key] || defaultStrat;
    options[key] = strat(parent[key], child[key], vm, key);
  }
  return options;
}
        💥 关键点 :
extends先于mixins合并,但最终 组件自身选项优先级最高。
三、不同选项的合并策略
🎯 1. 生命周期钩子(created、mounted 等)
- 合并方式 :合并为数组,依次执行。
 - 执行顺序 :
extends→mixins(从前到后) → 组件自身。 
            
            
              js
              
              
            
          
          // base.js
export default {
  created() { console.log('Base created'); }
}
// mixin.js
export const logMixin = {
  created() { console.log('Mixin created'); }
}
// Component.vue
export default {
  extends: BaseComponent,
  mixins: [logMixin],
  created() { console.log('Component created'); }
}
        输出:
Base created
Mixin created
Component created
        🎯 2. data 函数
- 合并方式 :不合并函数体 ,而是返回一个新函数,该函数调用 
parent.data()和child.data(),并 浅合并(shallow merge) 结果。 
            
            
              js
              
              
            
          
          // extends
data() { return { a: 1, b: 2 } }
// mixins
data() { return { b: 3, c: 4 } }
// 组件自身
data() { return { c: 5, d: 6 } }
        最终 data:
            
            
              js
              
              
            
          
          { a: 1, b: 3, c: 5, d: 6 }
        ⚠️ 注意:是对象的浅合并,不是递归合并。
🎯 3. methods、computed、watch
- 合并方式 :直接覆盖。
 - 优先级:组件自身 > mixins > extends。
 
            
            
              js
              
              
            
          
          // mixin
methods: { save() { console.log('Mixin save'); } }
// 组件
methods: { save() { console.log('Component save'); } }
        ✅ 调用 this.save() 时,执行的是 组件自身的 save。
🎯 4. props、inject、directives
- 合并方式 :浅合并对象 ,组件自身覆盖 
mixins和extends。 
            
            
              js
              
              
            
          
          // extends
props: { type: String, required: true }
// 组件
props: { type: Number } // 覆盖 type,但 required 仍存在
        ⚠️ 类型冲突可能导致警告。
🎯 5. components、filters(Vue 2)
- 合并方式 :浅合并对象,组件自身优先。
 
            
            
              js
              
              
            
          
          // mixin
components: { Button: ButtonA }
// 组件
components: { Button: ButtonB } // 覆盖
        四、实战:合并顺序演示
            
            
              js
              
              
            
          
          const Base = {
  name: 'Base',
  data() { return { base: 1, shared: 'base' }; },
  created() { console.log('Base'); }
};
const Mixin1 = {
  name: 'Mixin1',
  data() { return { mixin1: 2, shared: 'mixin1' }; },
  created() { console.log('Mixin1'); }
};
const Mixin2 = {
  name: 'Mixin2',
  data() { return { mixin2: 3 }; },
  created() { console.log('Mixin2'); }
};
export default {
  extends: Base,
  mixins: [Mixin1, Mixin2],
  data() { return { component: 4, shared: 'component' }; },
  created() { console.log('Component'); }
}
        最终选项:
| 选项 | 值 | 
|---|---|
name | 
'Component'(组件自身覆盖) | 
data | 
{ base:1, shared:'component', mixin1:2, mixin2:3, component:4 } | 
created | 
[Base.created, Mixin1.created, Mixin2.created, Component.created] | 
created 执行顺序:
Base
Mixin1
Mixin2
Component
        五、最佳实践与陷阱
✅ 最佳实践
mixins:用于横切关注点(如日志、权限、表单验证);extends:用于创建基类组件 (如BaseForm、BaseTable);- 优先级清晰:始终假设组件自身选项会覆盖其他来源。
 
❌ 常见陷阱
data浅合并:嵌套对象不会递归合并;- 命名冲突 :
methods被静默覆盖; - 生命周期混乱 :过多 
mixins导致钩子执行顺序难追踪。 
💡 Vue 3 Composition API 更好地解决了这些问题。
💡 结语
"理解
mergeOptions,才能驾驭组件复用的复杂性。"
| 合并源 | 处理顺序 | 优先级 | 
|---|---|---|
extends | 
1 | 中 | 
mixins | 
2(从前到后) | 中 | 
| 组件自身 | 最后 | 高 | 
| 选项类型 | 合并策略 | 
|---|---|
| 生命周期 | 数组,依次执行 | 
data | 
浅合并返回值 | 
methods | 
直接覆盖 | 
props | 
浅合并对象 | 
掌握这一机制,你就能:
✅ 预测组件行为;
✅ 设计可复用的 mixin;
✅ 避开合并陷阱;
✅ 平滑迁移到 Composition API。