【vue篇】Vue 组件继承与混入:mixin 与 extends 的合并逻辑深度解析

在 Vue 开发中,你是否使用过:

js 复制代码
import BaseComponent from './base';
import { formMixin } from './mixins';

export default {
  extends: BaseComponent,
  mixins: [formMixin, authMixin],
  // ...
}

"当 mixinextends 冲突时,谁的优先级更高?" "生命周期钩子的执行顺序是怎样的?" "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. 生命周期钩子(createdmounted 等)

  • 合并方式 :合并为数组,依次执行
  • 执行顺序extendsmixins(从前到后) → 组件自身。
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. methodscomputedwatch

  • 合并方式直接覆盖
  • 优先级:组件自身 > mixins > extends。
js 复制代码
// mixin
methods: { save() { console.log('Mixin save'); } }

// 组件
methods: { save() { console.log('Component save'); } }

✅ 调用 this.save() 时,执行的是 组件自身的 save


🎯 4. propsinjectdirectives

  • 合并方式浅合并对象 ,组件自身覆盖 mixinsextends
js 复制代码
// extends
props: { type: String, required: true }

// 组件
props: { type: Number } // 覆盖 type,但 required 仍存在

⚠️ 类型冲突可能导致警告


🎯 5. componentsfilters(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 :用于创建基类组件 (如 BaseFormBaseTable);
  • 优先级清晰:始终假设组件自身选项会覆盖其他来源。

❌ 常见陷阱

  1. data 浅合并:嵌套对象不会递归合并;
  2. 命名冲突methods 被静默覆盖;
  3. 生命周期混乱 :过多 mixins 导致钩子执行顺序难追踪。

💡 Vue 3 Composition API 更好地解决了这些问题。


💡 结语

"理解 mergeOptions,才能驾驭组件复用的复杂性。"

合并源 处理顺序 优先级
extends 1
mixins 2(从前到后)
组件自身 最后
选项类型 合并策略
生命周期 数组,依次执行
data 浅合并返回值
methods 直接覆盖
props 浅合并对象

掌握这一机制,你就能:

✅ 预测组件行为;

✅ 设计可复用的 mixin

✅ 避开合并陷阱;

✅ 平滑迁移到 Composition API。

相关推荐
LuckySusu3 小时前
【vue篇】Vue 中保持页面状态的终极方案:从卸载到缓存
前端·vue.js
IT_陈寒4 小时前
Python 3.11性能翻倍秘诀:7个你从未注意过的隐藏优化点!
前端·人工智能·后端
学习编程的Kitty5 小时前
算法——位运算
java·前端·算法
程序猿阿伟5 小时前
《3D动作游戏受击反馈:从模板化硬直到沉浸式打击感的开发拆解》
前端·网络·3d
jsonchao5 小时前
web 菜鸟级选手,纯好玩,做了 1 个小游戏网站(我感觉挺好玩😂)
前端
Onion5 小时前
解决 iframe 中鼠标事件丢失问题:拖拽功能的完整解决方案
前端·javascript·vue.js
Sailing5 小时前
🔥🔥「别再复制正则了」用 regex-center 一站式管理、校验、提取所有正则
前端·javascript·面试
GISer_Jing5 小时前
前端知识详解——HTML/CSS/Javascript/ES5+/Typescript篇/算法篇
前端·javascript·面试
一枚前端小能手5 小时前
🔧 jQuery那些经典方法,还值得学吗?优势与式微的真相一次讲透
前端·javascript·jquery