关于Vue3过渡动画的踩坑记录

 更新时间:2021年12月26日 22:23  点击:430 作者:黄轶黄老师

背景

在我的 《Vue 3 开发企业级音乐 App》课程问答区,有个同学提了个问题,在歌手列表到歌手详情页面到转场动画中,只有进入动画,却没有离场动画:

该学生确实在这个问题上研究了有一段时间,而且从他的描述,我一时半会儿也想不出哪有问题,于是让他把代码传到 GitHub 上,毕竟直接从代码层面定位问题是最靠谱的。

问题定位

一般遇到此类问题的时候,我的第一反应是他用的 Vue 3 版本可能有问题,毕竟 Vue 3 还在不断迭代过程,某个版本有一些小 bug 是很正常的,于是我把他的项目的 Vue 3 版本升级到了最新的 3.2.26。

但运行后发现,该问题仍然存在。我感到有些困惑,于是跑了一下自己课程项目源码,并没有复现该问题,然后我又把自己课程项目的 Vue 3 版本也升级到最新,仍然没有复现该问题。

通过上述分析,我基本排除了 Vue 3 版本的问题。本质上说,从歌手页面切换到歌手详情页无非就是打开歌手详情页这个二级路由页面,而从歌手详情页退回到歌手页面无非就是移除歌手详情页这个二级路由页面。于是我开始对比两边项目的歌手页面以及详情页的源码:

<!-- singer.vue -->

<template>

<div class="singer" v-loading="!singers.length">

  <index-list

    :data="singers"

    @select="selectSinger"

  ></index-list>

  <!-- 用router-view去承载二级路由 -->

<!--  <router-view :singer="selectedSinger"></router-view>-->

  <!-- vue3需要在router-view中使用transition, appear进入时候也会有动画 -->

  <router-view v-slot="{ Component }">

<!--  singer-detail返回动画无效 研究  -->

    <transition appear name="slide">

      <!-- component动态组件Component就是作用域插槽中的一个属性,这个是由router-view这个组提供的

       Component就是你的路由表中的路由组件

       exclude="singer-detail"排除不缓存数据的组件否则会缓存数据导致每次数据都不重新请求

       -->

        <component :is="Component"

                   :singer="selectedSinger"

        ></component>

    </transition>

  </router-view>

</div>

</template>



<!-- singer-detail.vue -->

<template>

  <!-- 因为通过二级路由实现,所以放在views下 -->

  <section class="singer-detail">

    <music-list

      :songs="songs"

      :title="title"

      :pic="pic"

      :loading="loading"

    ></music-list>

  </section>

</template>

上边是学生的代码,接下来贴一下我项目的源码:

<!-- singer.vue -->

<template>

  <div class="singer" v-loading="!singers.length">

    <index-list

      :data="singers"

      @select="selectSinger"

    ></index-list>

    <router-view v-slot="{ Component }">

      <transition appear name="slide">

        <component :is="Component" :data="selectedSinger"/>

      </transition>

    </router-view>

  </div>

</template>

<!-- singer-detail.vue -->

<template>

  <div class="singer-detail">

    <music-list

      :songs="songs"

      :title="title"

      :pic="pic"

      :loading="loading"

    ></music-list>

  </div>

</template>

经过对比,我感觉两边的源码差别并不大,除了该学生会用注释做一些学习笔记。一时间难以找出问题,于是我祭出了杀手锏——调试源码。因为毕竟对于 Vue 3 过渡动画的实现原理,我还是如数家珍的。

如果执行了退出过渡动画,则一定会执行 transition 组件包裹的子节点解析出的 leave 钩子函数。

于是我在 leave 钩子函数内部加了个 debugger 断点:

// @vue/runtime-core/dist/runtime.core-bundler.esm.js

leave(el, remove) {

  debugger

  const key = String(vnode.key);

  if (el._enterCb) {

    el._enterCb(true /* cancelled */);

  }

  // ...

}

接着运行项目,当我从歌手详情页回退到歌手页面的时候,发现并没有进入 debugger 断点,也就意味着 leave 钩子函数压根没有执行。

再往前追溯,对于即将卸载的节点,执行其 leave 钩子函数的时机是在执行 remove 函数时,于是我在 remove 函数内部打上断点:

// @vue/runtime-core/dist/runtime.core-bundler.esm.js

const remove = vnode => {

  debugger

  const { type, el, anchor, transition } = vnode;

  if (type === Fragment) {

    removeFragment(el, anchor);

    return;

  }

  if (type === Static) {

    removeStaticNode(vnode);

    return;

  }

  const performRemove = () => {

    hostRemove(el);

    if (transition && !transition.persisted && transition.afterLeave) {

      transition.afterLeave();

    }

  };

  if (vnode.shapeFlag & 1 /* ELEMENT */ &&

    transition &&

    !transition.persisted) {

    const { leave, delayLeave } = transition;

    const performLeave = () => leave(el, performRemove);

    if (delayLeave) {

      delayLeave(vnode.el, performRemove, performLeave);

    }

    else {

      performLeave();

    }

  }

  else {

    performRemove();

  }

};

接着再次运行项目,当我从歌手详情页回退到歌手页面的时候,虽然进入了断点,但也发现了一些代码的逻辑问题:从 vnode 解析到了对应的 transition 对象,由于其对应的 type 是 Fragment,执行进入了下面这段逻辑:

if (type === Fragment) {

  removeFragment(el, anchor);

  return;

}

直接返回并没有执行后续 transition 对象的 leave 钩子函数。我继续查看 vnode 的值,发现它有两个子节点,一个注释节点和一个 section 节点。我恍然大悟,原来是学生写的注释导致的问题:

<!-- singer-detail.vue -->

<template>

  <!-- 因为通过二级路由实现,所以放在views下 -->

  <section class="singer-detail">

    <music-list

      :songs="songs"

      :title="title"

      :pic="pic"

      :loading="loading"

    ></music-list>

  </section>

</template>

在 Vue 的模版解析中,遇到 HTML 注释,也会把它解析成一个注释节点,可以借助 Vue 3 的模版导出工具看一下它编译后的结果:

import { createCommentVNode as _createCommentVNode, resolveComponent as _resolveComponent, createVNode as _createVNode, createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

const _hoisted_1 = { class: "singer-detail" }

function render(_ctx, _cache) {

  const _component_music_list = _resolveComponent("music-list")

  return (_openBlock(), _createElementBlock(_Fragment, null, [

    _createCommentVNode(" 因为通过二级路由实现,所以放在views下 "),

    _createElementVNode("section", _hoisted_1, [

      _createVNode(_component_music_list, {

        songs: _ctx.songs,

        title: _ctx.title,

        pic: _ctx.pic,

        loading: _ctx.loading

      }, null, 8 /* PROPS */, ["songs", "title", "pic", "loading"])

    ])

  ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))

}

由于 Vue 3 支持了模版可以有不止一个的根节点,上述模版的根就会被解析成一个 Fragment 节点,这就导致了该组件在移除的时候并不会执行对应的过渡动画。

进一步分析

那么为啥 Fragment 节点就不需要过渡动画呢?我找到了代码对应的提交注释:

fix(fragment): perform direct remove when removing fragments This avoids trying to grab .el from hoisted child nodes (which can be created by another instance), and also skips transition check since fragment children cannot have transitions.

注释给的解释就是 Fragment 节点不可以有 transition 过渡。但这里还有一个问题,为什么这么写不会影响进入过渡动画呢?

因为在运行时执行组件 render 函数渲染组件的子树 subTree 的时候,renderComponentRoot 函数内部做了一些特殊处理:

function renderComponentRoot(instance) {

  let result

  // ...

  // call render funtion to get the result

  // attr merging

  // in dev mode, comments are preserved, and it's possible for a template

  // to have comments along side the root element which makes it a fragment

  let root = result;

  let setRoot = undefined;

  if ((process.env.NODE_ENV !== 'production') &&

    result.patchFlag > 0 &&

    result.patchFlag & 2048 /* DEV_ROOT_FRAGMENT */) {

    [root, setRoot] = getChildRoot(result);

  }

  // inherit transition data

  if (vnode.transition) {

    // ...

    root.transition = vnode.transition;

  }

  return result

}

在通过执行组件实例的 render 方法拿到渲染的子树后,在开发环境下通过 getChildRoot 函数对注释节点做了一层过滤,得到结果 root,并且给它的根节点继承了其 parent vnode 的 transition 对象。但是注意到,整个 renderComponentRoot 返回的还是 result 对象。

对于我们的示例 SingerDetil 歌手详情组件,它的子树 vnode 是一个 Fragment,但是在执行 renderComponentRoot 的时候,由于第一个节点是注释节点,则被过滤,只有后面的实体节点 singer-detail 对应的 vnode 才有 transition 属性,因此它有进入过渡动画。

但是在组件移除的时候,由于组件的子树 vnode 是一个 Fragment,因此不会有离开过渡动画。

总结

找到了 bug 的原因后,修复就很简单了,直接把注释节点删除即可,当然生产环境不会有该问题,因为在默认情况下,生产环境会删除注释节点。

从这个案例来看,写注释虽然是个好习惯,但是一不小心可能会踩了 Vue 3 的坑。

学会源码调试还是很重要的,如果不了解源码,遇到此类 bug 就会一脸懵逼,非常被动,因为文档不会告诉你原因。因此我还是鼓励大家多学习源码,通过调试源码,你才能最接近事实的真相。 

到此这篇关于关于Vue3过渡动画的踩坑记录的文章就介绍到这了,更多相关Vue3过渡动画踩坑内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

原文出处:https://mp.weixin.qq.com/s/as9kv-PibvPsvTIv7IMHZw

[!--infotagslink--]

相关文章

  • ps动态环绕动画效果怎么制作

    ps动态环绕动画效果是现在很多人都非常喜欢的,大多数人还不知道ps动态环绕动画效果怎么制作下面文章就给大家介绍下ps怎么制作科技感十足的动态环绕动画效果,一起来看看...2017-07-06
  • 浅谈vue2的$refs在vue3组合式API中的替代方法

    这篇文章主要介绍了浅谈vue2的$refs在vue3组合式API中的替代方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-18
  • Vue3 编译流程-源码解析

    今天将从 Vue 的入口文件开始,看看声明了一个 Vue 的单文件之后是如何被 compile-core 编译核心模块编译成渲染函数的。下面小编讲解并附上代码分析展现在文章里,感兴趣的小伙伴不要错过奥...2021-09-25
  • 详解vue过度效果与动画transition使用示例

    Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果,Vue 提供了内置的过渡封装组件transition,该组件用于包裹要实现过渡效果的组件...2021-10-10
  • jQuery动画效果相关方法实例分析

    这篇文章主要介绍了jQuery动画效果相关方法,结合实例形式较为详细的分析了jQuery实现动画效果所用到的常见方法与相关注意事项,需要的朋友可以参考下...2016-01-05
  • Vue3中reactive函数toRef函数ref函数简介

    这篇文章主要介绍了Vue3中的三种函数,分别对reactive函数toRef函数以及ref函数原理及使用作了简单介绍,有需要的朋友可以借鉴参考下...2021-09-24
  • vue3弹出层V3Popup实例详解

    这篇文章主要介绍了vue3弹出层V3Popup实例详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-04
  • vue3源码剖析之简单实现方法

    源码的重要性相信不用再多说什么了吧,特别是用Vue 框架的,一般在面试的时候面试官多多少少都会考察源码层面的内容,下面这篇文章主要给大家介绍了关于vue3源码剖析之简单实现的相关资料,需要的朋友可以参考下...2021-09-07
  • 利用PS制作“签名”GIF小动画的例子

    本文章来为各位介绍一篇利用PS制作“签名”GIF小动画的例子,希望这个制作过程能够帮助到各位朋友,各位进入看看吧。 先给大家看下效果,有兴趣的童鞋&rarr;_&rarr;可以...2016-09-14
  • 微信小程序实现登录页云层漂浮的动画效果

    微信小程序目前的火热程度相信不用多言,最近利用空余时间用小程序实现了个动态的登录页效果,所以下面这篇文章主要给大家介绍了利用微信小程序实现登录页云层漂浮动画效果的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。...2017-05-09
  • 公众号SVG动画交互实战代码

    这篇文章主要介绍了公众号SVG动画交互实战代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-01
  • js实现缓动动画

    这篇文章主要为大家详细介绍了js实现缓动动画,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-11-25
  • jQuery实现的给图片点赞+1动画效果(附在线演示及demo源码下载)

    这篇文章主要介绍了jQuery实现的给图片点赞+1动画效果,并附带在线演示及demo源码下载,涉及jQuery鼠标事件响应及页面元素属性动态操作相关技巧,需要的朋友可以参考下...2016-01-05
  • 利用Vue3 创建Vue CLI 项目(一)

    这篇文章主要介绍利用Vue3 创建Vue CLI 项目,下面文章内容附有官方文档链接,安装过程,需要的可以参考一下...2021-10-18
  • 浅谈Vue3的几个优势

    这篇文章主要给大家分享的是Vue3的几个优势,Vue3仍然在源码、性能和语法 API 三个大的方面进行了优化,下面我们一起进入文章看看具体详情吧...2021-10-26
  • jQuery实现的点赞随机数字显示动画效果(附在线演示与demo源码下载)

    这篇文章主要介绍了jQuery实现的点赞随机数字显示动画效果,并附带在线演示与demo源码供读者下载.涉及jQuery鼠标事件响应及基于animate动画效果实现技巧,需要的朋友可以参考下...2016-01-05
  • JS实现点击登录弹出窗口同时背景色渐变动画效果

    这篇文章主要介绍了JS实现点击登录弹出窗口同时背景色渐变动画效果,涉及JavaScript基于鼠标事件及时间函数定时触发形成渐变动画的相关技巧,需要的朋友可以参考下...2016-03-28
  • Photoshop利用智能对象将人物制作成花海里飘散消失的GIF动画教程

    今天小编在这里就来给Photoshop的这一款软件的使用者们来说一说利用智能对象将人物制作成花海里飘散消失的GIF动画的教程,各位想知道具体方法的使用者们,那么大家就快来...2016-09-14
  • 利用vue3+ts实现管理后台(增删改查)

    这篇文章主要介绍了利用vue3+ts实现管理后台(增删改查),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-30
  • three.js 实现露珠滴落动画效果的示例代码

    这篇文章主要介绍了three.js 实现露珠滴落动画效果的示例代码,非常不错,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-01