从Vue源码看Transition组件优化导致leave不触发问题

137 阅读1分钟

情景

整个情形非常简单

<template>
	<router-view v-slot="{ component }">
		<transition>
			<component :is="component" />
		</transition>
	</router-view>
</template>

一个非常简单的自定义路由,我现在要给他加一个动画。

这里我采用的是 @leave 这个事件,绑定到 transition 上,并且我把 transition 封装成了一个自定义组件

<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
>
  <!-- ... -->
</Transition>

问题

现在,问题就来了。

  1. 当我切换页面的时候,发现和 leave 相关的都没用,都没有触发。

排查

首先,我考虑的是 缓存,是不是因为 component 重复,导致没有触发更改。(参考 v-for key,还有 scroll 的 key)

所以,我给 component 加了一个 key

类似

<script>
export default {
  data() {
    return {
      count: 1,
      interval: null 
    }
  },
  mounted() {
    this.interval = setInterval(() => {
      this.count++;
    }, 1000)
  },
  beforeDestroy() {
    clearInterval(this.interval)
  }
}
</script>

<template>
  <Transition>
    <span :key="count">{{ count }}</span>
  </Transition>
</template>

然后发现并不是这个问题。

几经周折之下,我就去看了一下 vue transition 的代码

这个 transition 其实是基于 BaseTransition 的

我放到这里

useTransitionState

eventTrigger

我们可以发现,是因为触发了 onBeforeUnmount 这个生命周期,导致后续的事件触发走了优化,不必再执行繁琐的动画。但是对于我来说,我需要这个动画。

其实走到这里,解决方案就一目了然了。就是组件被销毁了,就不用再执行复杂的动画了。

解决方案

  1. keep-alive 这是最简单的解决方案。
<keep-alive>
	<transition>
		<component />
	</transition>  
</keep-alive>
  1. 组件播放动画 我们可以在组件退出之前,手动给组件播放动画,然后再调用真正的退出。
// 示例
async function exitWithAnimation() {
  await playLeaveAnimation(); // 播放动画
  router.back();              // 再进行返回
}

即,假设我们采用 router.back() 做退出处理。 我们可以先 播放动画 再 执行这个 back

希望 vue 官方能增加一个属性关闭这个优化。