vant icon indicating copy to clipboard operation
vant copied to clipboard

[Bug Report] 调用closeToast后,Toast组件没有同步消失

Open phil668 opened this issue 9 months ago • 1 comments

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组件在同一帧画面中出现。

  1. Chrome Performance面板的截图 Image

  2. console截图 Image

导致问题原因(供参考)

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}
        />
      );
    };
  1. 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)
    }
  },
  1. 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);

验证猜想

  1. 在chrome中,在vShow的transition.leave回调函数中打上debugger
  2. 在业务代码中的onClickPopup函数打上debugger
  3. 发现是onClickPopup先执行,然后transition.leave回调才执行

https://github.com/user-attachments/assets/84c2a129-f481-48b9-a154-960a5f41ed67

希望的修复方案

可以考虑扩展Popup的transition属性值吗,比如当为false时,Popup组件不被Transition组件包裹。

Reproduce Steps

  1. 打开Chrome Performance录制
  2. 点击show toast按钮,弹出Toast
  3. 在Toast存在期间,快速点击show popup按钮
  4. 发现录制的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,

phil668 avatar Mar 30 '25 13:03 phil668

其实不算是bug,因为在有trnasition组件的情况下代码执行顺序是符合预期的。应该作为feature request更合适

phil668 avatar Mar 30 '25 13:03 phil668