问题

看到了一个 vuetify的issue

在只绑定了 click 事件时,该按钮样式为不可点击,实际上是能够点击的:

1
2
3
<VList>
<VListItem title="i am button" @click="foo" />
</VList>

复现例子

简单来说就是按钮的样式不正确,并且在最后一个 beta 版本是正常的,3.0 发布后就寄了

简单排查后发现这样一段代码,用于判断按钮是否可点击:

1
2
3
4
5
6
const isClickable = computed(
() =>
!props.disabled &&
props.link !== false &&
(props.link || link.isClickable.value || (props.value != null && !!list))
);

其中最后一行的 link.isClickable 出自一个 hook,封装了根据传参构建链接对象的逻辑:

1
const link = useLink(props, attrs);

useLink 里面的 isClickable 是这样判断的:

1
2
3
4
const isClickable = computed(() => {
// 是否有值,或者绑定了点击事件
return isLink?.value || !!(attrs.onClick || attrs.onClickOnce);
});

attrs.onClick 这里出问题了

beta15 时,点击事件是通过透传传入子组件根元素的

而后面有一个提交,向组件中增加了点击事件的声明:

1
2
3
4
5
6
7
export default {
...
emits: {
click: (e: Event) => true,
},
...
};

attrs 存放的是未在组件内声明的传参和事件

这样会导致传入的 @click 事件不会出现在 attrs 中,导致判断该按钮不可点击

解决

如果需要在组件内判断外部是否有监听器,有两个方法

在 props 中声明监听器

例如有父组件:

1
<Child @click="doSth" />

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
// 声明 @click 传入的方法,这样就可以在 props 中获取到
defineProps({ onClick: Function });
defineEmits(['click']);
</script>

<template>
<div>
badge
<button v-if="onClick" @click="$emit('click')">delete</button>
</div>
</template>

示例

透传

我个人提交的 pr 用的是这个方法,毕竟 useLink 这个方法估计很多地方都用上了,不好动手,而且这样改只是还原了旧版的实现

父组件同上:

1
<Child @click="doSth" />

子组件通过 attrs 检查并绑定:

1
2
3
4
5
6
7
8
9
10
<script setup>
// 什么都不声明,好耶!
</script>

<template>
<div>
badge
<button v-if="$attrs.onClick" @click="$attrs.onClick">delete</button>
</div>
</template>

或者直接透传:

1
2
3
4
5
6
7
8
<script setup>
// 什么都不声明,好耶!
</script>

<template>
<!-- 会绑定到根组件上捏 -->
<button v-if="$attrs.onClick">delete</button>
</template>

示例