[Bug Report] 调用closeToast后,Toast组件没有同步消失
Reproduction Link
https://github.com/phil668/vant-toast-repo
Vant Version
4.9.18
Describe the Bug
具体现象
当通过api的方式使用Toast组件并设置forbidClick=true时。在调用closeToast后,Toast组件没有同步消失。此时若与其他元素产生click等交互行为,会导致视觉上Toast还存在,但页面已经可以点击,会被误认为Toast组件的forbidClick禁用效果未生效。
Toast组件调用方式和传参如下:
<script setup lang="ts">
import { closeToast, showToast } from "vant";
import { ref } from "vue";
const isShowPopup = ref(false);
const onClickPopup = () => {
console.log("click show popup");
const toast = document.querySelector(".van-toast");
if (toast) {
console.log("toast display", getComputedStyle(toast).display);
}
isShowPopup.value = true;
};
const onClickToast = async () => {
showToast({
duration: 0,
forbidClick: true,
message: "Loading...",
// NOTICE: 此处设置none的原因是不需要过渡效果,因此传none覆盖vant原有的默认过渡效果
transition: "none",
});
await new Promise((resolve) => setTimeout(resolve, 3000));
closeToast();
};
</script>
<template>
<van-button @click="onClickToast">show toast</van-button>
<van-button @touchstart="onClickPopup">show popup</van-button>
<van-popup
v-model:show="isShowPopup"
title="Terms"
position="bottom"
round
:close-on-click-overlay="false"
transition=""
closeable
:style="{ height: '30%' }"
>
</van-popup>
</template>
预期行为
由于toast组件没有过渡效果,因此需要等页面上的toast组件完全消失后,onClickPopup函数才能被调用,popup组件才会弹出。即popup和toast不会出现在同一帧画面中。
实际行为
在Toast组件还未消失时,onClickPopup已被调用,Popup和Toast组件在同一帧画面中出现。
-
Chrome Performance面板的截图
-
console截图
导致问题原因(供参考)
1.Toast组件基于Popup组件实现,而Popup组件是被Transition组件包裹的。
https://github.com/youzan/vant/blob/main/packages/vant/src/popup/Popup.tsx#L230-L245
const renderTransition = () => {
const { position, transition, transitionAppear } = props;
const name =
position === 'center' ? 'van-fade' : `van-popup-slide-${position}`;
return (
<Transition
v-slots={{ default: renderPopup }}
name={transition || name}
appear={transitionAppear}
onAfterEnter={onOpened}
onAfterLeave={onClosed}
/>
);
};
- vue的
v-show指令在组件被Transition组件包裹时,设置元素的display=none并不是同步的,而是在transition.leave函数回调中设置display=none。即在有transition组件的情况下,不是同步设置display=none。 https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/directives/vShow.ts#L27-L42
updated(el, { value, oldValue }, { transition }) {
if (!value === !oldValue) return
if (transition) {
if (value) {
transition.beforeEnter(el)
setDisplay(el, true)
transition.enter(el)
} else {
transition.leave(el, () => {
setDisplay(el, false)
})
}
} else {
setDisplay(el, value)
}
},
- Vant 在 show.value 置为 false 时立即解除点击锁定,但此时 DOM 的 display还未更新为none,导致
Toast还存在但已经可以点击其他元素。 https://github.com/youzan/vant/blob/main/packages/vant/src/toast/Toast.tsx#L137
watch(() => [props.show, props.forbidClick], toggleClickable);
验证猜想
- 在chrome中,在vShow的
transition.leave回调函数中打上debugger - 在业务代码中的
onClickPopup函数打上debugger - 发现是
onClickPopup先执行,然后transition.leave回调才执行
https://github.com/user-attachments/assets/84c2a129-f481-48b9-a154-960a5f41ed67
希望的修复方案
可以考虑扩展Popup的transition属性值吗,比如当为false时,Popup组件不被Transition组件包裹。
Reproduce Steps
- 打开Chrome Performance录制
- 点击show toast按钮,弹出Toast
- 在Toast存在期间,快速点击show popup按钮
- 发现录制的Frame中有一段时间 Toast和Popup是同时存在的
Device / Browser
浏览器版本:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36';设备:Model Name: MacBook Pro Model Identifier: MacBookPro18,1 Model Number: MK193CH/A Chip: Apple M1 Pro Total Number of Cores: 10 (8 performance and 2 efficiency) Memory: 16 G System: 14.6.1,
其实不算是bug,因为在有trnasition组件的情况下代码执行顺序是符合预期的。应该作为feature request更合适