组件之间的data作用域是独立的,不能相互访问的。所以我们需要通过一些特别的手法让它们之间实现数据通信。
常见的组件通讯方式分为三种:
- 父传子
- 子传父
- 同级互传
1. 父传子
分成两步:
- 在父组件中给子组件标签绑定属性,属性值为要传递的值。
- 在子组件中,通过
props
选项接收传递的属性,然后就可以直接使用了。
为了方便演示,这里采用HTML的组件写法:
<div id="app">
<!-- 直接在组件标签名上添加传递的属性与属性值 -->
<test :propmsg="msg" :product="good"></test>
</div>
<script>
var vm = new Vue({
data(){
return {
good: '《钢铁是怎样炼成的》',
msg: '孩儿啊,最近过的如何啊?'
}
},
components:{
// 注册子组件
test:{
// 通过 props 接收父组件传递的属性
props: ['propmsg', 'product']
template:"<div>我是子组件,接受来自父组件的值---{{ propmsg }} -- {{ product }}</div>"
}
}
}).$mount('#app')
</script>
注意:千万不要在子组件中去修改从父组件传递过来的属性。否则会报错:
因为Vue采用的是单向数据流,所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
prop 的命名规范
HTML 中的属性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的prop
名需要使用其等价的 kebab-case (短横线分隔命名) 命名,如下所示:
// 属性名是驼峰
props: ['postTitle']
<!-- 在 HTML 中使用 短横线 -->
<blog-post post-title="hello!"></blog-post>
有些时候我们可能想对父组件传递的属性做出一些限制,如类型、内容、是否具有默认值、是否必填等,这时候我们可以将props
写为对象形式:
new Vue({
// 改为对象形式,添加多种设置
props: {
propmsg: {
// 控制类型,多个类型可以写为数组形式 [String, Number, ...]
type: String,
// 默认值
default: '默认值',
// 是否必填,默认false
required: false,
// 自己定义的校验函数,参数为父组件传递的属性值,返回false则会在控制台报错
validator(value){
console.log('父组件传递的值为:', value)
return true/false
}
},
// 也可以直接写为 属性名: 类型
product: String
}
}).$mount('#app')
2. 子传父
子组件到父组件的通信,要借助Vue实例的$emit
方法,也是需要分成两步:
-
在子组件中,需要
$emit
一个事件 -
父组件中,在子组件的标签上绑定它
$emit
的事件
<div id="app">
<!-- 绑定子组件 $emit 的事件名 @子传的事件名='自己的监听函数' -->
<com @give="receive"></com>
</div>
<script>
var vm = new Vue({
el : "#app",
components: {
com: {
template: '<button @click="trans">给父组件传值</button>',
methods:{
trans(){
// this.$emit('事件名', 传递的值1, arg2, ...)
this.$emit('give', "闹,给你1000块打麻将")
}
}
}
},
methods: {
// 监听函数的参数为子组件传递的值
receive(msg){
console.log('接收到子组件传递的信息:', msg)
}
}
})
</script>
2.1 sync 修饰符
如果确实需要在子组件中修改父组件传递过来的值,可以借助sync
修饰符。
写法是在子组件标签上传递属性后面加上sync
修饰符,在子组件更改该属性值的时候传一个this.$emit('update:修改的属性名', 修改的值);
,就可以实现修改的效果。
<div id="app">
<!-- 绑定的属性添加 sync 修饰符 -->
<com :message.sync= "message"></com>
</div>
<script>
var vm = new Vue({
el : "#app",
data(){
return {
message: "父元素里面的值"
}
},
components: {
com: {
props: ["message"],
template: "<h3 @click='changeProp'>子组件 {{message}}</h3>",
methods:{
changeProp(){
// 使用 update:属性名 作为发射的事件名,修改父组件传递的属性值
this.$emit('update:message', "改变之后的")
}
}
}
}
})
</script>
3. 同级传递
同级的组件通过添加$bus
(中央事件总线)传递。
-
我们需要添加
$bus
方法,vue 的实例上没有这个方法 -
同级组件直接可以通过
$emit
和$on
实现值的传递
<div id="app">
<zujian1></zujian1>
<zujian2></zujian2>
</div>
<script>
// 给原型添加属性,所有的Vue实例都会拥有该属性
// 这里赋值为一个Vue实例,主要是要借用Vue实例上的 $emit 和 $on 方法
Vue.prototype.$bus = new Vue();
var vm = new Vue({
components: {
"zujian1": {
data(){
return {
msg: ''
}
}
created () {
// 实例可以使用原型的属性 $bus
// $on 是vue提供的方法,用于接收 $emit 的值,方式为:$on('$emit的事件名', 回调函数)
this.$bus.$on("busevent", val =>{
// 参数就是$emit传递的值
this.msg = val;
});
},
template :"<p>zujian2 传递过来的值是 {{msg}}</p>"
},
"zujian2": {
methods:{
send(){
// 类似于子传父
this.$bus.$emit("busevent", '好朋友,你在干什么?');
}
},
template :"<button @click='send'>给zujian1传递数据</button>"
},
}
}).$mount('#app')
</script>