序言:我们都知道vue父子组件通信主要通过props和事件,那还知道其他形式的通讯方式吗?本文将一一为你揭晓。
1、sync
修饰符 + this.$emit('update:属性名', data)
(父子组件)
1.1、sync修饰符的作用
在Vue中,子父组件最常用的通信方式就是通过props进行数据传递,props值只能在父组件中更新并传递给子组件,在子组件内部,是不允许改变传递进来的props值,这样做是为了保证数据单向流通。但有时候,我们会遇到一些场景,需要在子组件内部改变props属性值并更新到父组件中,这时就需要用到.sync修饰符。
如果我们在子组件中直接修改props中的属性值,将会报以下错误:
报错的大概意思就是:不允许直接修改props里面的属性值。
这里可以参考一篇文章:https://blog.csdn.net/XuM2222...
1.2、利用sync修饰符实现双向数据绑定
父组件:通过给绑定属性添加sync修饰符将值传递给子组件,父组件值改变,子组件随着改变。
子组件:通过this.$emit('update:属性名', data)来改变props属性值并更新父组件对应的值。
<template>
<div @click="click2change">
点击加一{{foo}}
<my-checkbox :checked.sync="foo"></my-checkbox>
</div>
</template>
<script>
import Vue from 'vue';
// 组件通信: props,事件,provide|inject,vuex, vuebus,。。。
const myCheckbox = Vue.component('my-checkbox', {
props: {
value: {
type: String,
default: 'testvalue'
},
checked: {
type: Number,
default: 0
}
},
methods: {
changeValue() {
//this.checked -= 2;//会报刚刚说的错误,不能直接修改props属性值
this.$emit("update:checked", this.checked - 2);
}
},
template: `
<div>
<div @click.stop="changeValue">checked: {{checked}}</div>
</div>
`
});
export default {
name: 'list',
components: {
myCheckbox,
},
data() {
return {
foo: 1
}
},
methods: {
click2change() {
this.foo += 1;
}
}
}
</script>
结果运行:
2、自定义组件v-model+this.$emit('自定事件名', data)(父子组件)
2.1、v-model语法糖
2.2、自定义组件v-model
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。model 选项可以用来避免这样的冲突:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
现在在这个组件上使用 v-model 的时候:
<base-checkbox v-model="lovingVue"></base-checkbox>
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的属性将会被更新。
注意:你仍然需要在组件的 props 选项里声明 checked 这个 prop。
2.3、使用自定义组件v-model实现双向数据绑定
父组件:通过v-model向子组件传值,并监听自定义函数更新值。
子组件:通过model选项自定义绑定属性名和方法,使用$emit触发自定义方法更新父组件的值。
<template>
<div @click="click2change">
点击加一{{foo}}
<my-checkbox v-model="foo"></my-checkbox>
</div>
</template>
<script>
import Vue from 'vue';
// 组件通信: props,事件,provide|inject,vuex, vuebus,。。。
const myCheckbox = Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
//需要在组件的 props 选项里声明 checked 这个 prop
checked: {
type: Number,
default: 0
}
},
methods: {
changeValue() {
this.$emit("change", this.checked - 1);
}
},
template: `
<div>
<div @click.stop="changeValue">checked: {{checked}}</div>
</div>
`
});
export default {
name: 'list',
components: {
myCheckbox,
},
data() {
return {
foo: 1
}
},
methods: {
click2change() {
// debugger
this.foo += 1;
}
}
}
</script>
运行结果:
3、value方式+this.$emit('input', data)(父子组件)
这种方式实现双向数据绑定是基于v-model语法糖
<template>
<div @click="click2change">
点击加一{{foo}}
<my-checkbox v-model="foo"></my-checkbox>
</div>
</template>
<script>
import Vue from 'vue';
// 组件通信: props,事件,provide|inject,vuex, vuebus,。。。
const myCheckbox = Vue.component('my-checkbox', {
props: {
value: {
type: Number,
default: 0
}
},
methods: {
changeValue() {
this.$emit("input", this.value - 1);
}
},
template: `
<div>
<div @click.stop="changeValue">value: {{value}}</div>
</div>
`
});
export default {
name: 'list',
components: {
myCheckbox,
},
data() {
return {
foo: 1
}
},
methods: {
click2change() {
// debugger
this.foo += 1;
}
}
}
</script>
运行结果:
4、props + $emit
(父子组件)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>props + $emit</title>
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
</head>
<body>
<!-- props + $emit -->
<div id="app"></div>
</body>
</html>
<script>
Vue.component('A', {
template: `
<div>
<span>{{ message }}</span>
<B :message="message" @messageListener="messageListener"></B>
</div>
`,
data() {
return {
message: '123'
}
},
methods: {
messageListener(val) {
this.message = val
}
}
})
Vue.component('B', {
template: `
<input type="text" v-model="myMessage" @input="messageChange(myMessage)"/>
`,
data() {
return {
// 这里是必要的,因为你不能直接修改 props 的值
myMessage: this.message
}
},
props: {
message: {
type: String,
default: ''
}
},
methods: {
messageChange(val) {
this.$emit('messageListener', val)
}
}
})
var app=new Vue({
el: '#app',
template: `
<div>
<A />
</div>
`
});
</script>
5、$attrs + $listeners
(祖孙组件)
上面这种组件通信的方式只适合直接的父子组件,也就是如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A直接想传递数据给组件C那就行不通了! 只能是组件A通过 props 将数据传给组件B,然后组件B获取到组件A 传递过来的数据后再通过 props 将数据传给组件C。当然这种方式是非常复杂的,无关组件中的逻辑业务一种增多了,代码维护也没变得困难,再加上如果嵌套的层级越多逻辑也复杂,无关代码越多!
针对这样一个问题,Vue 2.4
提供了$attrs
和 $listeners
来实现能够直接让组件A传递消息给组件C。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>$attrs + $listeners</title>
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
<script>
Vue.component('A', {
template: `
<div>
<span>{{ message + ' ' + name + ' ' + age + ' ' + school }}</span>
<B :message="message" :name="name" :age="age" school="school" @messageListener="messageListener" @nameListener="nameListener"></B>
</div>
`,
data() {
return {
message: 'hello',
name: 'tom',
age: 20,
school: '中南财经政法大学'
}
},
methods: {
messageListener(val) {
this.message = val
},
nameListener(val) {
this.name = val
}
}
})
Vue.component('B', {
template: `
<div>
<input type="text" v-model="myMessage" @input="messageChange(myMessage)">
<C v-bind="$attrs" v-on="$listeners"></C>
</div>
`,
data() {
return {
// 这里是必要的,因为你不能直接修改 props 的值
myMessage: this.message
}
},
props: {
message: {
type: String,
default: ''
}
},
methods: {
messageChange(val) {
this.$emit('messageListener', val)
}
}
})
Vue.component('C', {
template: `
<div>
<span>{{ $attrs.message }}<span>
<span>{{ $attrs.age }}<span>
<span>{{ $attrs.name }}<span>
<span>{{ $attrs.school }}<span>
<button @click="changeName">改变name</button>
</div>
`,
methods: {
// c组件的信息,怎么同步给a组件呢?
// 我们在b组件上 绑定 v-on=”$listeners”, 在a组件中,监听c组件触发的事件。就能把c组件发出的数据,传递给a组件。
changeName() {
this.$emit('nameListener', 'john')
}
}
})
var app=new Vue({
el: '#app',
template: `
<div>
<A />
</div>
`
});
</script>
将 $attrs
和 $listeners
单独拿出来说说吧!
-
$attrs
:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class
和style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定属性 (class和style
除外),并且可以通过v-bind="$attrs"
传入内部组件。 -
$listeners
:包含了父作用域中的 (不含.native
修饰器的)v-on
事件监听器。它可以通过v-on="$listeners"
传入内部组件。
6、中央事件总线 EventBus
(父子、祖孙、兄弟组件)
对于两个组件不是父子关系,那么又该如何实现通信呢?在项目规模不大的情况下,完全可以使用中央事件总线EventBus
的方式。如果你的项目规模是大中型的,那你可以使用我们后面即将介绍的Vuex 状态管理。
EventBus
通过新建一个Vue
事件bus
对象,然后通过bus.$emit
触发事件,bus.$on
监听触发的事件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件总线eventBus</title>
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
<script>
// 创建共享实例
const eventBus = new Vue()
Vue.prototype.$eventBus = eventBus
Vue.component('A', {
template: `
<div>
<B></B>
<C></C>
</div>
`,
data() {
return {}
},
})
Vue.component('B', {
template: `
<div>
<p>{{ message }}</p>
<button @click="sendC">发送给C</button>
<p>===================================</p>
</div>
`,
data() {
return {
message: ''
}
},
mounted() {
this.$eventBus.$on('recieve', val => {
this.message = val
})
},
methods: {
sendC() {
this.message = '我是B,C收到请回答'
this.$eventBus.$emit('send', this.message)
}
}
})
Vue.component('C', {
template: `
<div>
<span>{{ message }}</span>
<button @click="sendB">发送给B</button>
</div>
`,
data() {
return {
message: ''
}
},
mounted() {
this.$eventBus.$on('send', val => {
this.message = 'C收到了'
})
},
methods: {
sendB() {
this.$eventBus.$emit('recieve', 'B,我们多久没见了?')
}
}
})
var app=new Vue({
el: '#app',
template: `
<div>
<A />
</div>
`
});
</script>
7、provide + inject
(父子、祖孙)
关于provide/inject介绍:https://segmentfault.com/a/11...
熟悉 React 开发的同学对 Context API
肯定不会陌生吧!在 Vue 中也提供了类似的 API 用于组件之间的通信。
在父组件中通过 provider
来提供属性,然后在子组件中通过 inject 来注入变量。不论子组件有多深,只要调用了 inject
那么就可以注入在 provider 中提供的数据,而不是局限于只能从当前父组件的 prop 属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。这和 React 中的 Context API
有没有很相似!
⚠️ 注意:官网文档提及 provide 和 inject 主要为高阶插件/组件库提供用例(比如elementui),并不推荐直接用于应用程序代码中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>provide + inject</title>
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
<script>
Vue.component('A', {
template: `
<div>
<B></B>
</div>
`,
provide: {
msg: '1234124'
}
})
Vue.component('B', {
template: `
<div>
<label>B:</label>
<span>{{ this.msg }}</span>
<C></C>
</div>
`,
provide: {
msg: '42341234',
name: 'asdasda'
},
inject: ['msg'],
})
Vue.component('C', {
template: `
<div>
<label>C:</label>
<span>{{ this.xingming }}</span>
<span>{{ this.msg }}</span>
</div>
`,
inject: {
xingming: {
from: 'name',
default: ''
},
msg: {
from: 'msg',
default: ''
}
},
data() {
return {
}
},
})
var app=new Vue({
el: '#app',
template: `
<div>
<A />
</div>
`
});
</script>
8、$parent + $children
这里要说的这种方式就比较直观了,直接操作父子组件的实例。$parent
就是父组件的实例对象,而$children
就是当前实例的直接子组件实例了,不过这个属性值是数组类型的,且并不保证顺序,也不是响应式的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>$parent + $children</title>
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
<script>
Vue.component('A', {
template: `
<div>
<span>{{ msg }}</span>
<button @click="changeB">changeB</button>
<B></B>
</div>
`,
data() {
return {
msg: 'hello, world'
}
},
methods: {
changeB() {
// 操作子组件实例,$children是一个数组
this.$children[0].msg = 'hello, world'
}
},
})
Vue.component('B', {
template: `
<div>
<span>{{ msg }}</span>
<button @click="changeA">changeA</button>
</div>
`,
data() {
return {
msg: ''
}
},
methods: {
changeA() {
// 操作父组件实例
this.$parent.msg = 'welcome, you'
}
}
})
var app=new Vue({
el: '#app',
template: `
<div>
<A />
</div>
`
});
</script>
9、ref
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ref</title>
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
<script>
Vue.component('A', {
template: `
<div>
<button @click="changeB">changeB</button>
<B ref="comB"></B>
</div>
`,
methods: {
changeB() {
this.$refs.comB.msg = 'welcome, you'
}
},
})
Vue.component('B', {
template: `
<div>
<span>{{ msg }}</span>
</div>
`,
data() {
return {
msg: 'hello, world'
}
},
})
var app=new Vue({
el: '#app',
template: `
<div>
<A />
</div>
`
});
</script>
参考:
https://blog.csdn.net/songxiu...
https://juejin.im/post/5c77c4...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。