大厂技术  高级前端  Node进阶

点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群

背景

在我的 《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) {debuggerconst 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 => {debuggerconst { 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 对象,由于其对应的 typeFragment,执行进入了下面这段逻辑:

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 fragmentlet 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 dataif (vnode.transition) {// ...root.transition = vnode.transition;}return result
}

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

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

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

总结

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

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

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

Node 社群

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

 “分享、点赞、在看” 支持一波

​Vue 3 这个坑我踩了,你们一定要小心相关推荐

  1. vue在微信里面的兼容问题_详解Vue微信公众号开发踩坑全记录

    本文介绍了Vue微信公众号开发踩坑全记录,分享给大家,也给自己留个笔记. 需求 微信授权登录(基于公众号的登录方案) 接入JS-SDK实现图片上传,分享等功能 现状及难点 采用的Vue框架,前后端分 ...

  2. vuex modules ajax,VUE项目爬坑---6、vuex的真正存在的意义是什么

    VUE项目爬坑---6.vuex的真正存在的意义是什么 一.总结 一句话总结: a.[避免低效的数据传递]:当多个组件嵌套的时候,只能父子传递,那么中间的组件只能做传递数据之用,数据不是它用,它却传递 ...

  3. mysql int 转string_mysql的这些坑你踩过吗?快来看看怎么优化mysql

    什么是mysql? 如果你的回答是关系型数据库,那就会显得有些浅薄.我们平时工作中肯定会用到mysql,但是谈到mysql,就不能只说增删改查. 接下来我们从另一个角度认识一下mysql(其实不仅仅是 ...

  4. 把Java的nio坑逐个踩一遍

    epoll是个好东西好多地方都在直接或间接的用,nginx用event库用nio用你用我用大家用.LT模式省心ET模式牛掰,处理得当效率那真是杠杠的,C.C++的用法可以参考"项目" ...

  5. 怀旧服务器联盟优势,魔兽怀旧服:各阵营优势服务器推荐,这些深坑不要踩!...

    原标题: 魔兽怀旧服:各阵营优势服务器推荐,这些深坑不要踩! [布鲁] 布鲁服务器从公布名称到今日各种流言满天飞,目前从散人玩家角度看,这是一个部落优势服,因为部分魔兽主播及工作室强势进驻,反倒部分布 ...

  6. vue中微信分享的踩坑之旅

    最近基于vue做一个h5的项目,里面涉及到微信分享,当时心里想着,这微信分享不是分分钟的事嘛,而且自己年初还做个一个项目,也实现了微信自定义分享,代码都是现成的,妥妥的放心. 上周二上午花了1个小时, ...

  7. Vue SPA应用微信开发踩坑记录

    解决难点 JS-SDK需要向服务端获取签名,且获取签名中需要的参数包括所在页面的网址,但由于单页应用的路由特殊,其中涉及到的iOS和Android的微信客户端浏览器内核的差异性导致的兼容问题 JS-S ...

  8. vue项目国际化 vue-i18n以及踩坑解决 小姐姐手把手教你VUE国际化~

    1.安装配置 - 安装 $ npm install vue-i18n 或者: <script src="https://unpkg.com/vue/dist/vue.js"& ...

  9. vue中集成blockly的踩坑之旅

    blockly是一款可视化编辑器. blockly源码下载地址:https://gitee.com/mirrors/blockly?_from=gitee_search blockly的文档参考网址: ...

最新文章

  1. sklearn学习(一)
  2. c调用按钮点击事件_React中事件的写法总结
  3. oracle提供的有用函数(待续)
  4. php实现两个大整数求和,PHP计算两个特别大的整数实例代码
  5. mysql记录当前表数据的数据条数据类型_MySQL学习记录:数据类型与操作数据表...
  6. 刚刚,北京正式允许无人车上路路测!准入门槛500万元
  7. linux 编写sh文件,linux编写shell脚本程序one官方
  8. pygame放大图片_使用Pygame进行游戏开发(3)--绘图
  9. FPGA原理图设计----Arria II 系列FPGA设计(SATA)
  10. 论文期刊科普-SCI SSCI CSSCI CSCD和北大核心期刊分别是什么?
  11. 史上最全python常用英语单词,建议收藏
  12. python if嵌套/while嵌套/竞技叠杯
  13. 【建议收藏】Android初级开发者怎样快速提高开发技能?这20个开源APP能帮到你
  14. KeyTweak 键盘按键功能修改
  15. 【浙江大学C小程week1整理】
  16. AsyncQueryHandler 异步查询数据
  17. 单片机学多久能工作,单片机学好了能应聘什么工作?
  18. home staging_新西兰卖房时候的“房屋整体包装” Home Staging
  19. Learning Cocos2d-x for XNA(8)——Sprite到哪,我做主
  20. ds6k5b计算机联锁设备论文中期报告,DS6K5B计算机联锁设备(阅读).doc

热门文章

  1. stanford句法分析词性表
  2. Stanford-corenlp 英文词性标注
  3. MPAndroidChart的详细使用——BarChart条形图组(三)
  4. 用matlab作乌鸦喝水,仿真程序动画作品--乌鸦喝水
  5. SiamFC:用于目标跟踪的全卷积孪生网络 fully-convolutional siamese networks for object tracking
  6. win10:强力删除文件
  7. java编写定义圆类和圆柱体类,Java程序基础编程基础
  8. 在机器学习领域,主要的学习方式是哪几种?
  9. flashback机制
  10. python文件的打开模式有几种_以下选项中,不是Python打开文件模式的是( )_学小易找答案...