直奔主题
本文将介绍以下8组件通信方式:
- props/emit
- v-model
- v-solt
- refs/parent/children
- attrs/listeners
- provide/inject
- eventBus
- vuex
props和$emit方式
父子组件在传参时中常用的一种方式,父组件通过v-bind传入参数,子组件通过props来接收,子组件通过$emit方法传入事件名来触发一个事件,父组件通过v-on像监听原生DOM事件一样来监听这个事件。
xml
// 父组件CompFather
<template>
<div>
<comp-son
:title="title_name"
@changeTitle="changeTitle"
/>
<div>父组件的title_name:{{ title_name }}</div>
</div>
</template>
<script>
import CompSon from "./CompSon";
export default {
name: "CompFather",
components: {CompSon},
data() {
return {
title_name: "我是初始值"
}
},
methods: {
changeTitle(val) {
this.title_name = val;
}
}
}
</script>
xml
// 子组件CompSon
<template>
<div>
<div>子组件的titleName:{{title}}</div>
<button @click="changeTitle">改变</button>
</div>
</template>
<script>
export default {
name: "CompSon",
props: ['title'],
methods: {
changeTitle() {
this.$emit("changeTitle", "我变了");
}
}
}
</script>
适用场景:适用于直接父子关系(中间无嵌套组件)的组件间进行通信。
v-model方式
像单选框、复选框等输入控件,父组件通过v-model
传值给子组件时,会自动传递一个名为value
的prop属性,子组件只要通过this.$emit('input',val)
就能自动更改v-model绑定的值。
csharp
// App.vue
<add-item v-model="input_val" />
xml
<template>
<input :value="value" type="text" @input="$emit('input', $event.target.value)">
</template>
<script>
export default {
name: "AddItem",
// 如果对应的props字段名不叫value,则需要定义model属性来指定父组件的v-modal绑定的是哪个值
/*model: {
prop: "value1",
event: "input"
},*/
props: {
value: String
}
}
</script>
适用场景:v-model
适用于在封装input
类型的组件时,用于给数据进行双向绑定,如果不使用v-model
,则需要在父组件增加一个方法来监听事件然后改变父组件中的值,显得非常麻烦且不简洁。
v-solt
可以实现父子组件单向通信(父向子传值),在实现可复用组件,向组件中传入DOM节点、html等内容以及某些组件库的表格值二次处理等情况时,可以优先考虑v-slot。
xml
<template>
<div class="parent">
<h3>父组件</h3>
<input type="text" v-model="message" />
<Child>
<template v-slot:child>
{{ message }} <!--插槽要展示的内容-->
</template>
</Child>
</div>
</template>
<script>
import Child from './solt-child.vue'
export default {
name: 'solt-parent',
data() {
return {
message: '',
}
},
components: {
Child,
},
}
</script>
xml
<template>
<div class="child">
<h4>子组件</h4>
<p>收到来自父组件的消息:
<slot name="child"></slot> <!--展示父组件通过插槽传递的{{message}}-->
</p>
</div>
</template>
<math xmlns="http://www.w3.org/1998/Math/MathML"> r e f s / refs/ </math>refs/parent/$children
- refs
refs绑定在DOM元素上,可以获取dom元素,我们也可以将refs 绑定在子组件上,从而获取子组件实例。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。
xml
<template>
<comp1 ref="comp1"/>
</template>
<script>
export default {
name: "Comp-1",
data() {
value: ""
},
mounted: {
console.log("comp1的msg:",this.$refs.comp1.msg);
}
}
</script>
适用场景:当使用element-ui组件时,可用于调用组件方法,例如el-table
组件的选择表格项,排序等等。
- $parent/children
我们可以通过this.$parent
来获取当前组件的父组件实例(如果有的话),也可以通过this.$children
来获取当前组件的子组件实例。
注意的是:this.$children数组中下标不一定对应父组件引用的字组件顺序,例如如果有异步加载的子组件,可能会影响其在children中的顺序,所以使用时尽量使用name去查找对应子组件。
xml
// 父组件
<template>
<div class="parent">
<h3>父组件</h3>
<input type="text" v-model="message" />
<p>收到来自子组件的消息:{{ child1.message }}</p>
<Child />
</div>
</template>
<script>
import Child from './child'
export default {
name: 'Parent',
data() {
return {
message: '',
child1: {},
}
},
components: { Child }
mounted() {
this.child1 = this.$children.find((child) => {
return child.$options._componentTag === 'Child'
})
},
}
</script>
xml
// 子组件
<template>
<div class="child">
<h4>子组件</h4>
<input type="text" v-model="message" />
<p>收到来自父组件的消息:{{ $parent.message }}</p>
</div>
</template>
<script>
export default {
name: 'Child1',
data() {
return {
message: '', // 父组件通过this.$children可以获取子组件实例的message
}
},
}
</script>
<math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s 和 attrs和 </math>attrs和listeners
<math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s 和 attrs和 </math>attrs和listeners都是 Vue2.4 中新增加的属性,主要是用来开发高级组件的。
$attrs包含了除了props以外的所有父组件传递的参数(class
和 style
除外)。
props和 <math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s 是互补关系。父组件可以通过 ' v − b i n d = " attrs是互补关系。父组件可以通过`v-bind=" </math>attrs是互补关系。父组件可以通过'v−bind="attrs"`将参数传给子组件。
试想一下,当你创建了一个组件,你要接收 param1 、param2 ... 等数十个参数,如果通过 props,你需要通过props: ['aram1', 'param2', ......]等声明一大堆。如果这些 props 还有一些需要往更深层次的子组件传递,那将会更加麻烦。
而使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s ,你不需要任何声明,直接通过 attrs ,你不需要任何声明,直接通过 </math>attrs,你不需要任何声明,直接通过attrs.param1、$attrs.param2......就可以使用,而且向深层子组件传递也十分方便。
xml
<template>
<div>
<comp2 class="comp2" :style="comp2Style" :id="id" :msg="msg" :changeId="getChangedId" />
</div>
</template>
<script>
import Comp2 from "./comp2";
export default {
name: "comp-1",
components: { Comp2 }
data() {
return {
id: 1,
msg: "msg",
comp2Style: "color: pink"
}
},
methods: {
getChangedId (val) {
this.id = val
}
}
}
</script>
父组件给子组件comp2传了5参数:id,msg,style,class,还有一个方法changeId
xml
<template>
<div>组件2的$attrs:{{$attrs}}</div>
</template>
<script>
export default {
name: "comp-2",
props: {
id: Number
},
data() {},
mounted() {
console.log(this.$attrs); // props为{} 时 this.$attrs = { "id": 1, "msg": "msg", changeId: f(){} }
console.log(this.$attrs); // props为{ id: Numner} 时 this.$attrs = { "msg": "msg",changeId: f(){}}
console.log(this.$attrs); // props为{ msg: String, id: Number } 时 this.$attrs = { changeId: f(){} }
console.log(this.$attrs); // props为{ id: Number, msg: String, changeId: f(){}}时 this.$attrs = {}
}
}
</script>
适用场景:组件之间跨级传参,可以使用$attrs
属性,这样使得代码更加简洁,更利于维护。
- $listeners
包含了父作用域中v-on事件监听器,在创建高层次组件时很有用,用法和' <math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s ′ 类似。它可以通过 v − o n = " attrs'类似。它可以通过v-on=" </math>attrs′类似。它可以通过v−on="listeners"传入内部组件。
下面这个例子,共有三个组件:A、B、C,其关系为:[ A [ B [C] ] ],A为B的父组件,B为C的父组件。我们实现了:
-
父向子传值:组件A通过:messageFromA="message"将 message 属性传递给组件B,组件B通过$attrs.messageFromA获取到组件A的 message 。
-
跨级向下传值:组件A通过:messageFromA="message"将 message 属性传递给组件B,组件B再通过v-bind=" <math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s " 将其传递给组件 C ,组件 C 通过 attrs"将其传递给组件C,组件C通过 </math>attrs"将其传递给组件C,组件C通过attrs.messageFromA获取到组件A的 message 。
-
子向父传值:组件A通过@keyup="receive"在子、孙组件上绑定keyup事件的监听,组件B在通过v-on="$listeners"将 keyup 事件绑定在其 input 标签上。组件B input 输入框输入时,便会触发组件A的receive回调,将组件B的 input 输入框中的值赋值给组件A的 messageFromComp ,从而实现子向父传值。
-
跨层级向上传值:组件A通过@keyup="receive"在子孙组件上绑定keyup事件的监听,组件B再通过将其继续传递给C。组件C在通过v-on="$listeners"来将 keyup 事件绑定在其 input 标签上。当组件C input 输入框输入时,便会触发组件A的receive回调,将组件C的 input 输入框中的值赋值给组件A的 messageFromComp ,从而实现跨级向上传值。
xml
// 组件A
<template>
<div class="compa">
<h3>A组件</h3>
<input type="text" v-model="message" />
<p>收到来自{{ comp }}的消息:{{ messageFromComp }}</p>
<CompB :messageFromA="message" @keyup="receive" /> <!--监听子孙组件的keyup事件,将message传递给子孙组件-->
</div>
</template>
<script>
import CompB from './compB'
export default {
name: 'CompA',
data() {
return {
message: '',
messageFromComp: '',
comp: '',
}
},
components: { CompB },
methods: {
receive(e) { // 监听子孙组件keyup事件的回调,并将keyup所在input输入框的值赋值给messageFromComp
this.comp = e.target.name
this.messageFromComp = e.target.value
},
},
}
</script>
xml
// 组件B
<template>
<div class="compa">
<h3>B组件</h3>
<input type="text" v-model="message" />
<p>收到来自{{ comp }}的消息:{{ messageFromComp }}</p>
</div>
</template>
<script>
import CompB from './compB'
export default {
name: 'CompA',
data() {
return {
message: '',
messageFromComp: '',
comp: '',
}
},
components: { CompB },
methods: {
receive(e) { // 监听子孙组件keyup事件的回调,并将keyup所在input输入框的值赋值给messageFromComp
this.comp = e.target.name
this.messageFromComp = e.target.value
},
},
}
</script>
xml
// 组件C
<template>
<div class="compc">
<h5>C组件</h5>
<input name="compC" type="text" v-model="message" v-on="$listeners" /> <!--将A组件keyup的监听回调绑在该input上-->
<p>收到来自A组件的消息:{{ $attrs.messageFromA }}</p>
</div>
</template>
<script>
export default {
name: 'Compc',
data() {
return {
message: '',
}
},
}
</script>
provide/inject
在vue.js 的2.x 版本中添加了provide和inject选项。父组件通过provide来提供变量,子组件通过inject来注入变量,不论子组件有多深,只要调用了inject就可以注入provider中的数据,而不局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。
-
provide
和inject
只注重"源头"和"终点",主要用于在高阶组件库中使用,在平常的开发中一般不适用的原因是不方便追溯"源头",不知道是哪一层声明的和使用了。 -
子孙层中的
provide
会覆盖祖父层中相同key的属性值。 -
provide
选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。 -
inject
选项应该是一个字符串数组或一个对象,from
是选自祖父组件provide
的哪个值的key,default
指定一个默认值。 -
provide
和inject
绑定不是可响应的
。如果message是string类型,父组件通过input改变message后无法再赋值给messageFromA,如果provide
和inject
的是一个对象时,该数据是可响应的,当对象属性值改变后,messageFromA里的属性值还是可以随之改变的。子孙组件接收到的对象属性值也相应的改变了。
xml
// 1级组件A
<template>
<div class="compa">
<h3>this is A component</h3>
<input type="text" v-model="message.content" />
<CompB />
</div>
</template>
<script>
import CompB from './compB'
export default {
name: 'CompA',
provide() {
return {
messageFromA: this.message, // 将message通过provide传递给子孙组件
}
},
data() {
return {
message: {
content: '',
},
}
},
components: {
CompB,
},
}
</script>
xml
// 2级组件B
<template>
<div class="compb">
<h4>this is B component</h4>
<p>收到来自A组件的消息:{{ messageFromA && messageFromA.content }}</p>
<CompC />
</div>
</template>
<script>
import CompC from './compC'
export default {
name: 'CompB',
inject: ['messageFromA'], // 通过inject接受A中provide传递过来的message
components: {
CompC,
},
}
</script>
xml
// 3级组件C
<template>
<div class="compc">
<h5>this is C component</h5>
<p>收到来自A组件的消息:{{ messageFromA && messageFromA.content }}</p>
</div>
</template>
<script>
export default {
name: 'Compc',
inject: ['messageFromA'], // 通过inject接受A中provide传递过来的message
}
</script>
适用场景:适用于封装高阶组件,祖先实例不关心哪个后代实例会用到,后代实例不关心数据来源。
事件总线eventBus
使用中央事件总线实际就是创建一个vue实例,利用这个vue实例来传递消息。
注意:使用事件进行全局通信,容易让全局的变量的变化难以预测。
javascript
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.prototype.$bus = new Vue();
new Vue({
render: h => h(App),
}).$mount('#app')
kotlin
// 发送事件
this.$bus.$emit("myEvent", "bus msg");
// 接收事件
this.$bus.$on("myEvent", data => {
console.log(data);
})
适用场景:适用于规模不大的单页面应用的跨级跨兄弟组件间通信。
Vuex
Vuex 采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
vuex中一共有五个核心概念state、getter、Mutation、Action、Module
- state:提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存;
- getters: 类似于vue中的computed,进行缓存,对store中的数据进行加工处理成新的数据;
- mutations: 更改 state的状态的唯一方法是提交 mutation。mutation类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数;
- actions: 和mutation相似,mutation不能进行异步操作,如果要进行异步操作就要使用actions,触发一个action需要使用dispatch方法。
- modules:当遇见大型项目时,数据量大,store就会显得很臃肿,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块------从上至下进行同样方式的分割。点击链接查看vuex原理
javascript
import moduleA from './moduleA'
//1.安装插件
Vue.use(Vuex)
//2.创建对象
const store = new Vuex.Store({
state:{
counter:1000
},
mutations:{
changeCount (state, payoad) { // 同步修改counter数据
state.counter = state.counter + payload
}
},
actions:{
asyncChangeCount (store) { // 异步修改counter
setTimeout(() => {
store.commit('changeCount', 1)
}, 2000
}
},
getters:{
doubleCounter(state) {
return state.counter * state.counter
}
},
modules:{
a: moduleA
}
})
//3.导出使用
export default store
xml
<template>
<div>
<h2>{{counter}}</h2>
<button @click="changeStoreState">修改counter</button>
<button @click="asyncChange">异步修改counter</button>
</div>
</template>
<script>
export default {
name: 'App',
computed: {
counter () {
return this.$store.getters.counter
}
},
methods: {
changeStoreState () {
this.$store.commit('changeCount', 1)
},
asyncChange () {
this.$store.commit('asyncChangeCount', 12)
}
},
}
</script>
总结
通信方式 | 使用场景 |
---|---|
props/$emit | 直接父子组件传值 |
v-model | 封装需要双向绑定的组件时用v-model传值 |
v-solt | 在实现可复用组件,向组件中传入DOM节点、html等内容以及某些组件库的表格值二次处理等情况时 |
refs/parent/children | 可用于调用高阶组件方法,比如element-ui组件引用 |
attrs/listeners | 跨层级组件间传值,$listeners包含了父作用域中的 (不含native的) v-on 事件监听器 |
provide/inject | 用于高阶组件库,日常开发不常用,不利于维护 |
事件总线eventBus | 适用于跨层级或兄弟组件间通信 |
vuex | 用于大型单页应用,多个组件间需要共享状态 |