在前三篇文章中,我们学习了Props、ref、自定义事件、全局事件总线、PubSub、插槽等多种组件通信方式。今天我们来学习最后几个重要的知识点:v-model在组件间的使用 、样式作用域 、混合(mixins) 和插件(plugins)。掌握这些内容,你的Vue2知识体系将更加完整。
一、v-model双向数据绑定
1.1 基础用法
v-model 是Vue中最常用的指令之一,用于在表单元素上实现双向数据绑定。
<template>
<div>
<!-- 直接使用 v-model 实现双向数据绑定 -->
<input type="text" v-model="keyword">
<h3>当前输入的关键字:{{ keyword }}</h3>
</div>
</template>
<script>
export default {
data() {
return {
keyword: ''
};
}
};
</script>
1.2 v-model的实现原理
v-model 本质上是语法糖 ,它背后是由 :value 和 @input 组合而成的。
等价写法一:使用方法和事件
<template>
<div>
<!-- v-model的等价写法 -->
<input type="text" :value="keyword" @input="changeKey">
<h3>当前输入的关键字:{{ keyword }}</h3>
</div>
</template>
<script>
export default {
data() {
return {
keyword: ''
};
},
methods: {
changeKey(e) {
this.keyword = e.target.value;
}
}
};
</script>
等价写法二:直接赋值
<template>
<div>
<!-- 更简洁的等价写法 -->
<input type="text" :value="keyword" @input="keyword = $event.target.value">
<h3>当前输入的关键字:{{ keyword }}</h3>
</div>
</template>
<script>
export default {
data() {
return {
keyword: ''
};
}
};
</script>
v-model的本质:
v-model="keyword"
↓ 等价于
:value="keyword" + @input="keyword = $event.target.value"
二、组件间的v-model
2.1 在组件上使用v-model
v-model 不仅可以用于表单元素,还可以用在自定义组件上,实现父子组件之间的双向数据绑定。
父组件中使用v-model:
<template>
<div id="app">
<h3>父组件keyword:{{ keyword }}</h3>
<!-- 在组件上使用 v-model -->
<girl v-model="keyword"/>
</div>
</template>
<script>
import girl from './components/girl.vue';
export default {
name: 'App',
data() {
return {
keyword: ''
};
},
components: { girl }
};
</script>
2.2 组件v-model的实现原理
组件上的 v-model 同样是语法糖,等价于:
<!-- v-model的等价写法 -->
<girl :value="keyword" @input="inputFun"/>
<script>
methods: {
inputFun(val) {
this.keyword = val;
}
}
</script>
2.3 子组件实现v-model
子组件需要做两件事:
- 通过
props接收value - 通过
$emit触发input事件
子组件(girl.vue):
<template>
<div class="girl">
<h3>子组件keyword:{{ value }}</h3>
<!-- 输入框的值绑定props接收的value -->
<input type="text" :value="value" @input="sendKeyword">
</div>
</template>
<script>
export default {
name: 'girl',
// 默认接收 value 值
props: ['value'],
methods: {
sendKeyword(e) {
// 默认触发 input 事件,传递输入框的值
this.$emit('input', e.target.value);
}
}
};
</script>
2.4 v-model在组件中的完整流程
用户在子组件输入框中输入内容
↓
触发 @input 事件
↓
调用 sendKeyword(e) 方法
↓
this.$emit('input', e.target.value)
↓
父组件接收到 input 事件,更新 keyword 数据
↓
keyword 更新后,通过 :value 传递给子组件
↓
子组件展示最新的 value 值
2.5 组件v-model的核心约定
| 约定 | 说明 |
|---|---|
| props名称 | 默认为 value |
| 事件名称 | 默认为 input |
| 数据流向 | 父→子::value;子→父:$emit('input') |
如果需要修改默认的prop名称和事件名称,可以使用
model选项,这部分内容将在后续文章中介绍。
三、样式作用域(scoped)
3.1 样式冲突问题
在组件化开发中,如果不加限制,组件的样式会互相影响。
问题场景:
<!-- Girl.vue -->
<template>
<div class="girl">
<h2 class="title">girl组件</h2>
</div>
</template>
<style>
/* 不加scoped,这个样式会影响所有组件 */
.title {
background-color: blueviolet;
}
</style>
<!-- Son.vue -->
<template>
<div class="son">
<h2 class="title">son组件</h2>
</div>
</template>
如果两个组件都有 .title 类名,不加 scoped 会导致样式污染。
3.2 scoped解决方案
使用 scoped 属性可以让样式只作用于当前组件。
<template>
<div class="girl">
<h2 class="title">girl组件</h2>
</div>
</template>
<style scoped>
/* 这里的样式只作用于当前组件 */
.title {
background-color: blueviolet;
color: white;
padding: 10px;
}
</style>
3.3 scoped的原理
当给 <style> 加上 scoped 属性后,Vue会自动做两件事:
- 给组件中的所有HTML元素添加一个唯一的属性 (如
data-v-7ba5bd90) - 修改CSS选择器,添加对应的属性选择器
编译前:
<style scoped>
.title {
color: red;
}
</style>
编译后:
.title[data-v-7ba5bd90] {
color: red;
}
生成的HTML:
<h2 class="title" data-v-7ba5bd90>girl组件</h2>
这样就保证了样式只会作用于当前组件的元素。
3.4 使用建议
| 场景 | 建议 |
|---|---|
| 组件内部样式 | ✅ 使用 scoped |
| 全局公共样式 | 在 App.vue 或单独的文件中定义,不加 scoped |
| 覆盖第三方组件样式 | 使用深度选择器 /deep/ 或 >>> |
四、混合(mixins)
4.1 什么是mixins?
mixins(混合) 是一种分发Vue组件中可复用功能的方式。当多个组件有相同的配置项(如data、methods、生命周期等)时,可以将这些公共部分提取出来,通过mixins混入到各个组件中。
4.2 定义混合对象
mixins/handlerMsg.js:
export const handlerMsg = {
data() {
return {
num: 123,
commonData: '公共数据'
};
},
methods: {
getMsg() {
console.log('来自mixins的方法');
return this.num;
},
showCommon() {
alert('这是公共方法');
}
},
mounted() {
console.log('mixins的mounted执行了');
}
};
4.3 局部使用混合
在需要的组件中引入并使用:
<template>
<div class="girl">
<h2>girl组件</h2>
<h3>来自mixins的num:{{ num }}</h3>
<button @click="getMsg">调用mixins方法</button>
</div>
</template>
<script>
import { handlerMsg } from '../mixins/handlerMsg.js';
export default {
name: 'girl',
// 使用混合
mixins: [handlerMsg],
data() {
return {
girlMsg: 'girl组件自己的数据'
};
}
};
</script>
4.4 全局注册混合
在 main.js 中全局注册,所有组件都会拥有混合中的内容:
// main.js
import Vue from 'vue';
import App from './App.vue';
import { handlerMsg } from './mixins/handlerMsg.js';
// 全局注册混合
Vue.mixin(handlerMsg);
new Vue({
render: h => h(App)
}).$mount('#app');
4.5 混合的合并策略
当混合对象和组件本身有相同的配置项时,Vue有一套合并规则:
| 配置项 | 合并策略 | 示例 |
|---|---|---|
| data | 合并,冲突时以组件为准 | 混合有 num: 123,组件有 num: 456 → 结果为 456 |
| methods | 合并,冲突时以组件为准 | 同名方法,组件的会覆盖混合的 |
| 生命周期钩子 | 都会执行,混合的先执行 | 先执行混合的 mounted,再执行组件的 mounted |
示例验证:
// 混合对象
const mixin = {
data() {
return {
num: 123,
mixinData: '混合数据'
};
},
methods: {
showMsg() {
console.log('混合的showMsg');
}
},
mounted() {
console.log('混合的mounted');
}
};
// 组件
export default {
mixins: [mixin],
data() {
return {
num: 456, // 覆盖混合的num
compData: '组件数据'
};
},
methods: {
showMsg() { // 覆盖混合的showMsg
console.log('组件的showMsg');
}
},
mounted() {
console.log('组件的mounted');
}
};
// 结果:
// data: { num: 456, mixinData: '混合数据', compData: '组件数据' }
// methods: showMsg → 输出"组件的showMsg"
// mounted执行顺序:先输出"混合的mounted",再输出"组件的mounted"
4.6 mixins的使用场景
| 场景 | 说明 |
|---|---|
| 公共数据 | 多个组件共用的数据 |
| 公共方法 | 多个组件共用的方法 |
| 公共生命周期逻辑 | 多个组件都需要在某个生命周期执行相同的操作 |
| 公共计算属性 | 多个组件共用的计算属性 |
五、插件(plugins)
5.1 什么是插件?
插件 是用来给Vue做功能增强 的。一般情况下,我们自己去创建插件的可能性不大,但在实际开发中,有很多已经封装好的插件可以直接拿来使用,例如:vuex、vue-router 等。
5.2 使用插件的步骤
使用插件通常分为三步:
-
导入插件
-
注册插件 :
Vue.use(plugin) -
使用插件提供的功能
// 第一步:导入插件
import Vuex from 'vuex';
import VueRouter from 'vue-router';// 第二步:注册插件
Vue.use(Vuex);
Vue.use(VueRouter);// 第三步:使用插件提供的功能
const store = new Vuex.Store({ /* ... / });
const router = new VueRouter({ / ... */ });new Vue({
store,
router,
render: h => h(App)
}).$mount('#app');
5.3 定义自己的插件
插件是一个对象 ,对象中必须有 install 方法,这个方法会在 Vue.use() 时被自动调用。
plugins.js:
// 每一个插件都是一个对象
// 每一个插件对象中必须有一个install方法,这个install方法会被自动调用
// install方法中的参数包括两个部分:
// 第一部分:Vue构造函数
// 第二部分:可以接受用户在使用这个插件时传过来的数据,参数个数不限制
const plugins1 = {
install(Vue, x, y, z) {
console.log('插件install方法被调用了');
console.log('Vue构造函数:', Vue);
console.log('传递的参数:', x, y, z);
// 可以在install方法中做一些全局配置
// 例如:注册全局组件
Vue.component('my-component', {
template: '<h3>通过插件注册的全局组件</h3>'
});
// 例如:给Vue原型添加方法
Vue.prototype.$myMethod = function() {
console.log('通过插件添加的全局方法');
};
// 例如:注册全局指令
Vue.directive('my-directive', {
bind(el, binding) {
el.style.color = binding.value;
}
});
}
};
export default plugins1;
5.4 使用自定义插件
main.js:
import Vue from 'vue';
import App from './App.vue';
import plugins1 from './plugins/plugins.js';
// 使用插件,可以传递参数
Vue.use(plugins1, '参数1', '参数2', '参数3');
new Vue({
render: h => h(App)
}).$mount('#app');
5.5 插件中可以做什么?
在插件的 install 方法中,可以做很多事情:
| 功能 | 代码示例 |
|---|---|
| 注册全局组件 | Vue.component('comp-name', { ... }) |
| 注册全局指令 | Vue.directive('dir-name', { ... }) |
| 添加全局方法/属性 | Vue.prototype.$method = function() {} |
| 添加全局mixin | Vue.mixin({ ... }) |
| 添加实例方法 | Vue.prototype.$myMethod = function() {} |
5.6 常用Vue插件
| 插件 | 作用 | 安装 |
|---|---|---|
vuex |
状态管理 | npm install vuex |
vue-router |
路由管理 | npm install vue-router |
axios |
HTTP请求 | npm install axios |
element-ui |
UI组件库 | npm install element-ui |
vant |
移动端UI库 | npm install vant |
六、核心概念总结
6.1 本章知识点一览
| 知识点 | 核心要点 | 适用场景 |
|---|---|---|
| v-model | :value + @input 的语法糖 |
表单双向绑定、父子组件双向数据绑定 |
| 组件v-model | 子组件接收 value,触发 input 事件 |
封装可双向绑定的自定义组件 |
| scoped | 给样式添加唯一属性选择器 | 防止组件样式冲突 |
| mixins | 抽取公共配置项,分发到多个组件 | 多个组件共享data/methods/生命周期 |
| plugins | 通过 install 方法增强Vue功能 |
全局组件、指令、方法的注册 |
6.2 最佳实践建议
| 场景 | 推荐方案 |
|---|---|
| 表单组件封装 | 使用组件v-model实现双向绑定 |
| 组件样式隔离 | 始终使用 scoped |
| 公共逻辑复用 | 使用mixins抽取公共部分 |
| 全局功能增强 | 封装为插件,通过 Vue.use() 注册 |
| 状态管理 | 使用Vuex插件 |
6.3 注意事项清单
- ✅ v-model在组件上默认使用
value和input,可通过model选项自定义 - ✅ scoped样式中,子组件的根元素可以被子组件的样式影响
- ✅ mixins的生命周期钩子会先于组件的生命周期钩子执行
- ✅ 插件中的
install方法接收的第一个参数是Vue构造函数,不是实例 - ✅ 全局注册mixins会影响所有组件,谨慎使用
至此,Vue2组件通讯系列的四篇文章已经全部完成。从基础的Props到高级的事件总线、插槽、v-model、mixins和plugins,你已经掌握了Vue2更加完整的组件通信知识体系。