组件间通信
组件间通信的基本原则
- 不要在子组件中直接修改父组件的状态数据
- 数据在哪, 更新数据的行为(函数)就应该定义在哪
数据定义在哪个组件?
若多个组件公共一份数据,那么这个数据就定义在他们共同的父组件上
更新数据的函数方法定义在哪个组件?
数据在哪个组件, 那更新数据的行为(函数/方法)就定义,
后面可能需要将这些函数传递给需要调用的后代组件
后代组件不要直接更新父组件传递的数据, 而应该调用函数来更新
props : 父 ——> 子
非函数属性(对象/数组/基本类型)
子组件不要直接修改接收到的属性(props), 只是让你子组件去读取的
如果子组件想要更新父组件的数据 ==> 父组件需要向子组件传递更新数据的函数, 子组件调用(请看下节——函数属性)
使用步骤:
1). 通过标签属性
props:['comments']
2). 父组件中给子组件标签指定属性
App中 <List :comments="comments"/>
3). 子组件中声明接收标签属性:
接收的所有标签属性都会成为子组件对象的属性, 在模板中可以直接在子组件中使用
{{comments}}
子组件声明接收属性的方式:
方式一:
props: ['comments'], // 指定接收属性的属性名
方式二:
props: {
comments: Array // 指定接收属性的属性名和属性值类型
}
方式三:
props: {
sex: { // 指定属性的属性名/属性值的类型 / 默认属性值 / 是否必须的
type: Number,
default: 1, // 一般在不是必须时使用
required: true, // 即使指定default值, 也必须传递标签属性
}
}
函数属性
函数属性是从父组件传递给子组件,子组件调用了父组件传递的函数属性, 但能显示的数据是从子组件传递给了父组件,最终更新了父组件的数据
父组件:App.vue
<template>
<div>
<header class="site-header jumbotron">
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>请发表对Vue的评论</h1>
</div>
</div>
</div>
</header>
<div class="container">
<Add :addComment="addComment"/>
<List :comments = "comments" :deleteComment="deleteComment"/>
</div>
</div>
</template>d
<script type="text/ecmascript-6">
import Add from '@components/Add'
import List from '@components/List'
export default {
data(){
return{
comments:[
{id:1, username : 'A' , content : 'hello'},
{id:2, username : 'B' , content : 'hi'},
{id:3, username : 'C' , content : '你好'},
{id:4, username : 'D' , content : '嗨'}
]
}
},
methods:{
addComment(comment){
this.comments.unshift(comment)
},
deleteComment(index){
this.comments.splice(index,1)
}
},
components:{
Add,
List
}
}
</script>
子组件:item.vue
<template>
<div>
<li class="list-group-item">
<div class="handle">
//在子组件中定义一个删除函数
<a href="javascript:;" @click = "delected">删除</a>
</div>
<p class="user"><span >{{comment.username}}</span><span>说:</span></p>
<p class="centence">{{comment.content}}</p>
</li>
</div>
</template>
<script type="text/ecmascript-6">
export default {
//通过 props 传基本属性 以及 删除属性
props:['comment',"deleteComment","index"],
data(){
return{
}
},
methods: {
delected(){
if(window.confirm("你确定删除这条评论吗?")){
//在子组件中定义一个删除函数,并调用传过来的函数属性
this.deleteComment(this.index)
}
}
},
}
</script>
自定义事件($emit) : 子 ——> 父
第一步:触发事件(子组件) : this.$emit
子组件Header.vue:
<template>
<div class="todo-header">
//在子组件中定义一个函数increase,触发事件
<input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="increase" v-model = "content"/>
</div>
</template>
<script type="text/ecmascript-6">
export default {
data(){
return{
content : ''
}
},
methods:{
increase(){
let content = this.content.trim();
if(!content || content === ''){
return
}
let todo = {
id : Date.now(),
content,
completed : false
}
// 分发事件,将 todo 数据传给父组件
this.$emit('addTodo' , todo)
this.content = ''
}
}
}
</script>
第二步:绑定事件监听(父组件)
上面子组件this.$emit 的第一个参数要跟这个组件@后面的名称一致
父组件App.vue:
//将子组件传递给来的addTodo函数,绑定到父组件定义的add函数
<Header @addTodo="add"/>
... ...
methods:{
//todo参数是从子组件接收过来的
add(todo){
this.todos.unshift(todo)
},
}
或者:
<Header ref="header"/>
... ...
mounted () {
this.$refs.header.$on('addTodo', this.add)---正确
this.$refs.header.$on('addTodo', (todo) => {this.add})---正确
this.$refs.header.$on('addTodo', function(todo){this.add})--错误
},
methods:{
//父组件定义的addTodo函数
add(todo){
this.todos.unshift(todo)
},
}
第三步:解绑事件(父组件)
父组件App.vue:
beforeDestroy ( ) {
This.$refs.header.$off (‘ addTodo’)
}
全局事件总线—任意组件间通信
Vue.prototype.$Bus = new Vue();
-------------------------------------------------------------------------
new Vue({
beforeCreate () { // 尽量早的执行挂载全局事件总线对象的操作
Vue.prototype.$globalEventBus = this
},
}).$mount('#root')
mounted(){
this.$globalEventBus.$on('deleteTodo', this.deleteTodo);
},
methods:{
deleteTodo(name){
this._name = name
}
}
methods:{
deleteTodo(){
this.$globalEventBus.$emit('deleteTodo', this.name)
}
}
数据从 $emit 传到 $on
this.$globalEventBus.$off('deleteTodo')
消息订阅与发布—任意组件间通信
npm install -S pubsub-js
import PubSub from 'pubsub-js'
// 引入
import Vue from 'vue';
import App from './App'
import PubSub from 'pubsub-js'
Vue.config.productionTip = false;
new Vue({
beforeCreate() {
Vue.prototype.$globalEventBus = this;
Vue.prototype.$PubSub = PubSub;
},
el:"#root",
render : h => h(App)
})
(2)this.token = PubSub.subscribe(‘msgName’,(msgName, data) => { } ) // 订阅消息, 返回token —— mounted()
mounted() {
this.$refs.header.$on('addTodo' , this.addTodo);
this.$globalEventBus.$on('deleteTodo',this.deleteTodo)
this.token = this.PubSub.subscribe('updateTodo', (msgName,{todo, isTrue})=>{
this.updateTodo(todo, isTrue)
})
},
(3)PubSub.publish(‘msgName’, data): 发布消息, 触发订阅的回调函数调用
this.PubSub.publish('updateTodo', {todo:this.todo , isTrue : value})
数据从 publish 传到 subscribe
(4)PubSub.unsubscribe(this.token/msgName): 取消消息的订阅——beforeDestory()
beforeDestroy() {
this.$PubSub.unsubscribe(this.token)
},
beforeDestroy() {
this.$PubSub.unsubscribe("updateTodo")
},
async
功能与v-model类似, 可实现父子组件间的相互通信, 更准确的是双向数据同步
父组件:
<comp :foo.sync="bar"></comp>
<comp :foo="bar" @update:foo="val => bar = val"></comp>
子组件:
this.$emit('update:foo', newValue) //将 bar 更新为 newValue
[ 例 ] 弹窗的关闭事件:
<template>
<div class="details">
<myComponent :show.sync='valueChild' style="padding: 30px 20px 30px 5px;border:1px solid #ddd;margin-bottom: 10px;"></myComponent>
<button @click="changeValue">toggle</button>
</div>
</template>
<script>
import Vue from 'vue'
Vue.component('myComponent', {
template: `<div v-if="show">
<p>默认初始值是{{show}},所以是显示的</p>
<button @click.stop="closeDiv">关闭</button>
</div>`,
props:['show'],
methods: {
closeDiv() {
this.$emit('update:show', false); //触发 input 事件,并传入新值
}
}
})
export default{
data(){
return{
valueChild:true,
}
},
methods:{
changeValue(){
this.valueChild = !this.valueChild
}
}
}
</script>
$attrs / $listeners
父组件与任意子孙组件的通信
$ attrs: 排除props声明, class, style的所有组件标签属性组成的对象
$ listeners: 组件标签绑定的所有自定义事件监听的对象
一般: v-bind 与 $ attrs配合使用, v-on与 $ listeners配合使用
<el-button v-bind = " $ attrs" v-on = " $ listeners " ></el-button>
Provider / Inject
父组件与任意子孙组件的通信
由于vue有$parent属性可以让子组件访问父组件。但孙组件想要访问祖先组件就比较困难。通过provide/inject可以轻松实现跨级访问祖先组件的数据
$parent / $children & $refs
父子组件通信
在父组件中通过refs 获取子组件的DOM,然后可以直接在父组件中调用子组件的方法
通过this.$ parent 、 this.$ children 直接获取、使用父组件或子组件的属性和方法