当我们在封装vue组件时,难免会用到插槽slot来进行子组件的嵌套,那么,当slot插槽处的内容组件需要和父组件进行通信时,怎么办呢?场景代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// parent.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
methods: {

},
}
</script>


// child.vue
<template>
<div>
<input @input="onInput">
</div>
</template>
<script>
export default {
methods: {
onInput(){}
},
}
</script>

使用的时候,如下:

1
2
3
<parent>
<child></child>
</parent>

首先,我们都知道,在vue中,子组件向父组件进行通信传值时,是借用在子组件中派发自定义事件,然后在父组件中的子组件引用之处进行监听自定义事件,如下(@test=”demo”):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// parent.vue
<template>
<div>
<child @test="demo"></child>
</div>
</template>
<script>
import child from 'child.vue'
export default {
components:{child},
methods: {
demo(val) {
console.log(val)
}
},
}
</script>


// child.vue
<template>
<div>
<input @input="onInput">
</div>
</template>
<script>
export default {
methods: {
onInput(e) {
this.$emit('test', e.target.value)
}
},
}
</script>

但是,插槽slot是无法进行自定义事件的监听的,换言之,此种写法会报错:

1
<slot @test="fn"></slot>

那么,怎么解决这种问题呢?其实方法有很多,笔者这里说下自己的解决方案:

我们可以在子组件中调用父组件去触发一个自定义事件,然后在父组件自己的生命周期函数中对这个自定义事件进行监听,code如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// parent.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
mounted(){
this.$on('test', this.fn)
},
methods: {
fn(val){
console.log(val)
}
},
}
</script>


// child.vue
<template>
<div>
<input @input="onInput">
</div>
</template>
<script>
export default {
methods: {
onInput(e){
this.$parent.$emit("test", e.target.value)
}
},
}
</script>

在这里说白了,就是要找到包含slot的组件的Vue实例的引用,然后让这个组件的实例自己去触发那个约定好的自定义事件,然后由于它自己在自己的Vue实例初始化的时候已经对这个自定义事件进行了监听$on,所以当它自己的实例以$emit的形式触发该自定义事件的时候,所监听的事件就会触发相应的回调,从而达到通信的目的。

要找到此实例也很容易,像上述利用$parent($children)的形式只是其中的一种,我们再来看一下另外一种形式($refs):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// parent.vue (包含slot的组件)
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
mounted(){
this.$on('test', this.fn)
},
methods: {
fn(val){
console.log(val)
}
},
}
</script>

// 引用组件parent的地方
// Test.vue
<template>
<div>
<parent ref='parent'>
<button @click='clickFn'>点我</button>
</parent>
</div>
</template>
<script>
import Parent from './parent.vue'
export default {
components: {parent},
methods: {
clickFn() {
this.$refs.parent.$emit('test', 666)
}
},
}
</script>

还有一种方式是利用在Vue的原型上挂载一个Vue实例,即eventBus,利用Vue实例的eventBus进行跨组件间的通信;再更进一步也可以利用vuex,但是个人觉得无此必要。

这样即可解决slot插槽与其父组件的通信问题了!

另符vue官方文档对于监听自定义事件的说明:vue中$on的说明