Open cuixiaorui opened 2 years ago
重现:https://codesandbox.io/s/zealous-surf-ngryc Parent:
<template>
<div>
<Mid @myEvent="myEventHandler" />
</div>
</template>
<script>
import Mid from "./Mid.vue";
export default {
components: { Mid },
methods: {
myEventHandler() {
console.log("triggered");
},
},
};
</script>
Mid:
<template>
<div>
<Child v-bind="$attrs" />
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
emits: ["myEvent"],
inheritAttrs: false,
components: { Child },
mounted(){
console.log("Mid: ",this.$attrs)
}
};
</script>
Child:
<template>
<button @click="$emit('myEvent')">Emit Child</button>
</template>
<script>
export default {
mounted() {
console.log("Child: ", this.$attrs);
},
};
</script>
以上点击Child的Emit Child按钮不会触发myEventHandler,以下是当前的输出:
Child: Proxy {__vInternal: 1}
Mid.vue:17 Mid: Proxy {__vInternal: 1}
我们期待的应该是以下的输出:
Child: Proxy {__vInternal: 1, onMyEvent: ƒ}
Mid.vue:15 Mid: Proxy {__vInternal: 1, onMyEvent: ƒ}
这里可以知道其中的原因了,onMyEvent
被忽略掉了,通过查询文档可知,
Events listed in the emits option will not be inherited by the root element of the component and also will be excluded from the $attrs property.
这个不能算是bug,所以原issue里提供了2种解决方案。
props
代替emits
https://github.com/vuejs/vue-next/issues/4736#issuecomment-934156497是否有个好的机制$attrs
在emits
声明的onXXX
可以继续保留?
我比较赞同@adamreisnz的观点
https://github.com/vuejs/rfcs/discussions/397#discussioncomment-1780988
注释掉emits: ["myEvent"]
也可能达成目的。
emits
的意义在于确保不会造成冲突,Parent
无法知道事件是Mid
还是Child
触发的。
issues问题🔗如下:
https://github.com/vuejs/vue-next/issues/4736
复现流程:
Parent:
<template>
<div>
<Mid @myEvent="myEventHandler" />
</div>
</template>
<script>
import Mid from "./Mid.vue";
export default {
components: { Mid },
methods: {
myEventHandler() {
console.log("triggered");
},
},
};
</script>
Mid组件:
<template>
<div>
<Child v-bind="$attrs" />
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
emits: ["myEvent"],
inheritAttrs: false,
components: { Child },
mounted(){
console.log("Mid: ",this.$attrs)
}
};
</script>
Child组件:
<template>
<button @click="$emit('myEvent')">Emit Child</button>
</template>
<script>
export default {
mounted() {
console.log("Child: ", this.$attrs);
},
};
</script>
操作点击Child组件的Emit Child按钮,并没有触发Parent的myEventHandler事件
PS: 其实这个issues并不是真正的bug,却引发了讨论
首先咱们就来看看为什么,首先从Parent入手:
1.Parent要做的只有一件事,引入Mid组件,并将自身的myEventHandler方法传给Mid,传值方式为:@myEvent
2.Mid组件用emits接收了myEvent事件,并在mounted挂载阶段输出了this.$attrs
3.Child组件mounted挂载阶段输出了this.$attrs,并且在按钮的点击事件中调用了emit定义的myEvent
最后的结果为:
Child: Proxy {__vInternal: 1}
Mid: Proxy {__vInternal: 1}
并没有从$attrs中获取到myEvent,最后的myEvent也没有调用成功
$emit传递数据到Mid组件后并没有继续传递到Child,而是把$attrs传到Child组件上,而从刚才输出的MId可以发现,并没有输出$emit传递的myEvent,可以得知,$attrs并不会传递给定义给组件的emit选项,我们试着将Mid修改一下,去掉emits: ["myEvent"],可得到如下输出:
Child: Proxy {__vInternal: 1, onMyEvent: ƒ}
Mid: Proxy {__vInternal: 1, onMyEvent: ƒ}
由此得知,$attrs并不会传递emits选项中的事件,这引发了一个关于vue-next框架的讨论,有兴趣可以看看
对应
issues
号为: #4736
问题: v-bind:$attrs
没有透传
Parent:
<template>
<div>
<Mid @myEvent="myEventHandler" />
</div>
</template>
<script>
import Mid from './Mid.vue'
export default {
components: { Mid },
methods: {
myEventHandler() {
console.log('triggered')
}
}
}
</script>
Mid:
<template>
<div>
<Child v-bind="$attrs" />
</div>
</template>
<script>
import Child from './Child.vue'
export default {
emits: ['myEvent'],
inheritAttrs: false,
components: { Child },
mounted() {
console.log('Mid: ', this.$attrs)
}
}
</script>
Child:
<template>
<button @click="$emit('myEvent')">Emit Child</button>
</template>
<script>
export default {
mounted() {
console.log('Child: ', this.$attrs)
}
}
</script>
以上点击 Child 的 Emit Child 按钮不会触发 myEventHandler,以下是当前的输出:
Child: Proxy {__vInternal: 1}
Mid.vue:17 Mid: Proxy {__vInternal: 1}
其实这不是一个 bug
是一个需求
在我们封装高级组件/二次封装组件的时候,很多时候,需要利用 v-bind="$attrs"
绑定到子组件,继承传递 非 prop 的属性
在vue2
的时候, 非声明属性继承,是分为两类, 属性: $attrs
和事件: $listeners
所以,在 V2
中,分开传递给子组件没有什么问题,
但是在 V3
去除了 $listeners
这个 API
,使得所有非声明属性和事件传递,都会在 $attrs
里面进行传递,详情可以看 尤大 的文档 0031-attr-fallthrough.md
为了区分原生DOM
事件和自定义事件, V3
提供了 emits
声明当前组件,对外暴露的事件
{
emits: ['xxx']
}
这个时候, $attrs
里面需要排除 props
和 emits
两个部分的声明
官方文档声明 禁用 Attribute 继承
通过将
inheritAttrs
选项设置为false
,你可以使用组件的$attrs
property 将 attribute 应用到其它元素上,该 property 包括组件props
和emits
property 中未包含的所有属性 (例如,class
、style
、v-on
监听器等)。
props
代替emits
emits property blocks $attrs injection vuejs/vue-next#4736 (comment)这个 issues
的作者,是想在扩展组件的时候,可以通过 v-bind="$attrs"
处理子组件属性和事件
作者想通过 $attrs
访问祖先节点透传的事件, 但是被 emits
截胡了,想官方能够提供一种方式,所以产生了一个 讨论,大家可以去围观一下
emits 选项中声明的属性,不会再作为$attrs 中的一部分
This is especially important because of the removal of the .native modifier. Any listeners for events that aren't declared with emits will now be included in the component's $attrs, which by default will be bound to the component's root node.
这尤为重要,因为我们移除了 .native 修饰符。任何未在 emits 中声明的事件监听器都会被算入组件的 $attrs,并将默认绑定到组件的根节点上。
在 varlet组件库中,作者使用如下将 onClick 作为组件的一个 props 属性,那么我们使用组件可以通过 @click
的方式来触发click
事件
<template>
<div @click="handleClick">触发事件<div>
</template>
<script>
export default {
name: 'comp',
props: {
onClick: {
type: Function
}
},
methods: {
handleClick(e) {
this.onClick(e)
}
}
}
</scrip>
在使用的时候后
<comp @click="handleClick"></comp> // 此时通过原件的props传入,在使用的时候,可以通过 `@...`的方式传入
这种方案似乎和https://github.com/vuejs/rfcs/discussions/397#discussioncomment-1796052 的解决方案一致
关于 emits
选项,我认为适用在原生事件场景中
<template>
<div @click="handleClick">
clickTest
</div>
</template>
<script>
export default {
methods: {
handleClick() {
this.$emit('click')
}
}
}
</script>
如果此时我们在外使用组件然后触发click
事件的时候
<comp @click="handleClick"> // 此时如果点击则会触发两次click事件,即执行两次handleClick。
所以上面这种情况就需要在原组件中定义emits
属性来约束其向父组件触发事件的行为
emits property blocks $attrs injection #4736
为什么要读他
可以学到什么
开始时间
2021-12-20