经过之前的学习,我们已经大致了解到了数据在组件之间的传递
- 父组件通过
props
属性将数据传递给子组件 - 子组件通过自定义事件将数据传递给父组件
那么我们之前所了解到的都是父子组件之间的数据传递,那么有时候我们就会碰到不是父子关系的组件也需要数据的传递,这个时候我们怎么办呢?
不是父子关系的组件可能会有下面的两种情况:
- 两个组件,有一个共同的父组件的兄弟组件.
- 两个组件之间并没有太大的关系,甚至要找好几层才能找到共同的祖先组件.
1.兄弟组件之间的数据传递
兄弟关系的组件拥有一个共同的父组件,那么就是说我们可以在父组件上定义一个数据,加入这两个组件分别为 A
和 B
,我们希望子组件 A
的数据传递给 B
组件.
那么我们就可以先将 A
组件中的数据通过自定义事件传递给父组件,然后父组件再通过 props
将数据传递给组件 B
,这样我们就达到了将 A
组件的数据传递给 B
组件的目的.
一个简单的案例:
<div id="app">
<one-com :count="count"></one-com>
<two-com @add = "addNumber"></two-com>
</div>
<script>
let oneCom= ({
props:["count"],
template:`
<div>
<p>你点击了{{count}}次</p>
</div>
`
})
let TwoCom = ({
template: `
<button @click = "handleClick">点击+1</button>
`,
methods:{
handleClick() {
this.$emit("add")
}
}
})
let vm = new Vue({
el:"#app",
data:{
count:0,
},
methods:{
addNumber() {
this.count++;
console.log(222)
}
},
components:{
oneCom,TwoCom
}
})
</script>
点击组件2中的按钮,组件一中的数据会发生改变.
上面是一个简单易懂的兄弟组件之间数据传递的案例,下面我们来看一个比较经典的todoList
的案例,先展示传统的写法:
<!--全部都写在实例化对象中-->
<div id="app">
<div>
<input type="text" v-model = "message">
<button @click="addBtn">发表</button>
</div>
<div>
<h2>评论内容</h2>
<ul>
<li v-for="(comment,index) in comments" :key="index" >
{{comment}}
<button @click = "delBtn(index)">X</button>
</li>
</ul>
</div>
</div>
<script>
let vm = new Vue({
el :"#app",
data:{
message: '',
comments:[],
},
methods:{
//点击发表,评论区会增加一条内容
addBtn() {
this.comments.push(this.message)
this.message = ""
},
//点击删除,会删掉对应的评论区内容
delBtn(index) {
// console.log(index);
this.comments.splice(index,1)
}
}
})
</script>
将它改写为组件的形式,利用的就是兄弟组件之间的数据传递:
<!--拆分成组件的形式-->
<div id="app">
<my-input @add = "addComment"></my-input>
<show-comment :comments = "comments" @delete = "deleteComment"></show-comment>
</div>
<!--发表评论部分组件-->
<template id="MyInput">
<div>
<input type="text" v-model = "message">
<button @click="addBtn">发表</button>
</div>
</template>
<!--展示评论部分组件-->
<template id="ShowComment">
<div>
<h2>评论内容</h2>
<ul>
<li v-for="(comment,index) in comments" :key="index" >
{{comment}}
<button @click = "delBtn(index)">X</button>
</li>
</ul>
</div>
</template>
<script>
let MyInput = ({
template:"#MyInput",
data(){
return{
message:"",
}
},
methods:{
addBtn() {
let message = this.message.trim();
if(message === "") {
console.log("请输入合法的评论内容");
return;
}
this.$emit("add",message);
this.message = "";
}
}
})
let ShowComment = ({
props:["comments"],
template:"#ShowComment",
methods:{
delBtn(index) {
//console.log(index)
this.$emit("delete",index);
}
}
})
let vm = new Vue({
el:"#app",
data:{
comments:[],
},
methods:{
addComment(msg) {
this.comments.push(msg);
},
deleteComment(index){
//删除对应的评论
this.comments.splice(index,1);
}
},
components:{
MyInput,ShowComment
}
})
</script>
在这个过程中我们利用的是父组件实现的兄弟组件之间的数据传递.
2. Bus/总线/发布订阅模式
用于解决平级组件或者跨级组件之间的通信,因为如果全部用父子组件通信,有的不是父子组件就很繁琐,所以我们利用一个公共的 vue
实例订阅和发布事件来达到不是父子组件间的通信
其实就是利用 $emit()
方法触发自定义事件 和 $on()
监听自定义事件,给自定义事件添加事件处理函数
<div id="app">
<one-com ></one-com>
<two-com ></two-com>
</div>
<script>
Vue.prototype.$bus = new Vue();
let oneCom= ({
template:`
<div>
<p>你点击了{{count}}次</p>
</div>
`,
data(){
return{
count:0,
}
},
mounted(){
console.log(this)
let This = this
this.$bus.$on("increment", function(num){
console.log(This)
This.count+= num
})
}
})
let TwoCom = ({
template: `
<button @click = "handleClick">点击+1</button>
`,
methods: {
handleClick() {
this.$bus.$emit("increment",1)
}
}
})
let vm = new Vue({
el:"#app",
components:{
oneCom,TwoCom
}
})
</script>
通过示例我们机会发现,我们不需要每次都将数据传来传去, 变得复杂.
发布/订阅模式很好的帮我们处理了非父子组件间的数据传递.
但是回头在思考一下, 我们需要每次都实例化一个vue来作为我们的总线吗?其实不需要, 如果每次都创建vue实例反而不好, 因为帮我们处理发布/订阅模式的总线一个就好, 那么我们可以如何处理呢,
其实我们可以将这个总线的vue实例绑定为Vue原型上, 这样在每个vue实例上都可以使用这个总线.