前面介绍了父组件如何通过 prop 向子组件传递数据,反过来,子组件如何向父组件通信呢?
在 Vue.js 中,这是通过自定义事件来实现的,子组件使用 $emit()
方法触发事件,父组件使用 v-on
指令监听子组件的自定义事件。
// evenName: 事件名
// args: 事件传递的参数
vm.$emit( evenName, [...args] )
<div id="app">
<child @greet="sayHello">child>
div>
<script src="vue.js">script>
<script>
Vue.component('child', {
props: [],
data() {
return {
name: '张三'
}
},
methods: {
handleChick() {
this.$emit('greet', this.name)
}
},
template: ``
});
new Vue({
el: '#app',
methods: {
sayHello(name) {
alert("Hello, " + name)
}
}
});
script>
<div id="app">
<post-list>post-list>
div>
<script src="vue.js">script>
<script>
// 父组件
Vue.component('PostList', {
data() {
return {
posts: [
{
id: 1, title: '《Spring Boot实践》', author: '张三', date: '2019-10-21 20:10:15', vote: 0},
{
id: 2, title: '《Vue.js入门》', author: '李四', date: '2019-10-10 09:15:11', vote: 0},
{
id: 3, title: '《Python数据分析》', author: '王五', date: '2019-11-11 15:22:03', vote: 0}
]
}
},
methods: {
// 自定义事件vote的事件处理器方法
handleVote(post) {
post.vote = ++post.vote
return post
}
},
template: `
`
});
// 子组件
Vue.component('PostListItem', {
methods: {
handleVote() {
// 触发自定义事件
this.$emit('vote');
}
},
props: ['post'],
template: `
标题:{
{ post.title }} | 发帖人:{
{ post.author }} | 发帖时间:{
{ post.date }} | 点赞数:{
{ post.vote }}
`
});
let vm = new Vue({
el: '#app'
});
script>
在组件上也可以监听原生事件,在使用 v-on
命令时,添加一个 .native
修饰符即可。如:
<base-input @focus.native="onFocus">base-input>
这种方式最终是在组件的根元素上添加了 focus
(聚焦)事件的监听,如果组件模板的根元素是 ,那没有问题,但是如果不是,就有问题了。如:
Vue.component('MyInput', {
template: `
})
根元素是 ,相当于在
上添加了
focus
事件监听器,这时,父级的 .native
监听器将静默失败,它不会报错
,但是 onFocus 处理函数不会被如期被调用
。
为了解决这个问题,Vue.js 提供了一个 $listeners
属性,它是一个对象,里面包含了作用在这个组件上的所有监听器,如:
{
focus(event) {
...},
input(value) {
...},
...
}
有了 $listeners
属性,就可以使用 v-on="$listeners" 将组件上的所有事件监听器发送到特定的子元素。对于需要那些使用 v-model 的元素(如 )来说,可以为这些监听器创建一个新的计算属性,如下面:
<div id="app">
<my-input :label="title" v-model="msg" @focus="onFocus">my-input>
<p>{
{ msg }}p>
div>
<script src="vue.js">script>
<script>
Vue.component('MyInput', {
inheritAttrs: false,
// 父级传入数据:title -> label;msg -> value
props: ['label', 'value'],
data() {
return {
}
},
computed: {
inputListeners() {
let vm = this
// 将所有的对象合并为一个新对象
return Object.assign({
},
// 从父级添加的所有监控
this.$listeners,
// 添加自定义监控器或覆写一些监听器的行为
{
// 确保组件和 v-model 一起工作
input(event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
`
});
new Vue({
el: '#app',
data: {
title: '输入框:',
msg: '请输入'
},
methods: {
onFocus() {
console.log("不要摸人家么,好痒,臭流氓!")
}
}
});
script>
在某些情况下,可能需要对一个组件的 prop 进行双向绑定,Vue.js 推进以 update: myPropName 模式触发事件来实现。例如:
<div id="app">
<span>父组件计数值:{
{
counter }}</span>
<!-- <child :val="counter" @update:val="addCounter"></child>-->
<!-- $event:自定义事件的附加参数 -->
<child :val="counter" @update:val="counter = $event"></child>
</div>
<script src="vue.js"></script>
<script>
Vue.component('child', {
props: {
val: {
type: Number,
default: 0
}
},
data() {
return {
count: this.val
}
},
methods: {
handleChick() {
this.$emit('update:val', ++this.count)
}
},
template: `
子组件计数值:{
{ val }}
`
});
new Vue({
el: '#app',
data: {
counter: 0
},
methods: {
addCounter(val) {
return this.counter = val;
}
}
});
</script>
为了方便起见,Vue.js 为了上述这种模式提供了一个缩写,即 .sync
修饰符(在 v-bind 指令上使用),修改如下:
<child :val="counter" @update:val="counter = $event">child>
<child :val.sync="counter">child>
当用一个对象同时设置多个 prop 的时候,也可以将 .sync
修饰符和 v-bind
一起使用:
<text-document v-bind.sync="doc">text-document>
这里会把 doc
对象中的每一个属性作为一个单独的 prop 传进去,然后为每个属性添加 v-on:update 监听器。
<body>
<div id="app">
<span>父组件 post:{
{ post.title }} | {
{ post.author }} | {
{ post.time }} | {
{ post.vote }} | {
{ post.price }}span>
<child v-bind.sync="post">child>
div>
<script src="vue.js">script>
<script>
Vue.component('child', {
props: {
title: {
type: String },
author: {
type: String },
time: {
type: String },
vote: {
type: Number },
price: {
type: Number },
},
data() {
return {
title: this.vote,
author: this.vote,
time: this.vote,
vote: this.vote,
price: this.price
}
},
methods: {
handleChick() {
this.$emit('update:vote', this.vote += 1)
this.$emit('update:price', this.price += 4)
}
},
template: `
子组件 post:{
{ title }} | {
{ author }} | {
{ time }} | {
{ vote }} | {
{ price }}
`
});
new Vue({
el: '#app',
data: {
post: {
title: '《Spring Boot 从入门到入土》',
author: '张三',
time: '2021年05月16日00:05:50',
vote: 0,
price: 0
}
},
methods: {
}
});
script>
注:本篇主要来源:《Vue.js 从入门到实践》第十一章 组件,作者:孙鑫,出版社:中国水利水电出版社