vue-router4过度动画无效解决方案
在初次使用vue3 + vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操作配置都成了过去式了。所以借手头正在做的项目,进行简单的总结。
官方给出的结构
开始前,请仔细阅读vue-router-transition使用的官方文档https://next.router.vuejs.org/zh/guide/advanced/transitions.html
在 Vue Router API 从 v3(Vue2)到 v4(Vue3)的重写过程中,大部分的 Vue Router API 都没有变化,但是在迁移你的程序时,你可能会遇到一些破坏性的变化。
transition
和keep-alive
现在必须通过v-slot
API 在RouterView
内部使用:
<router-view v-slot="{ Component }">
<transition>
<keep-alive>
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
关于内置组件<component :is>请见我的另一篇文章component加:is实现动态组件的渲染
Component就是作用域插槽中的一个属性,这个是由router-view这个组提供的 ,你可以查看一下插槽作用域这部分文档https://cn.vuejs.org/v2/guide/components-slots.html#作用域插槽,Component就是你的路由表中的路由组件;为什么必须是Component可以查看文档https://next.router.vuejs.org/api/#route
router/index.js中对路由的配置
import { createRouter, createWebHashHistory } from 'vue-router';
import Detail from 'components/detail/detail';
import MainList from 'components/main-list/main-list';
const router = createRouter({
history: createWebHashHistory(),
routes: [
// 定义当访问的时候,默认跳转到的路由
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'mainlist',
component: MainList,
// 路由元信息定义每个路由进入退出的不同动画
meta: { transition: 'aside-right' }
},
{
path: '/detail',
name: 'detail',
component: Detail,
meta: { transition: 'aside-right' }
}
]
});
export default router;
关于过度动画不生效
过度动画不生效情况可能有两种:
1、transition样式不正确,没加appear(https://v3.cn.vuejs.org/api/built-in-components.html#transition apper属性等详解);
2、组件上没有加keep-alive;
组件上没有加keep-alive的时候,会出现进入的动画正常,而退出时候的动画无效;从DOM结构上看就是DOM节点一瞬间被删除了;
transition样式不正确可能导致进入动画无效,但是退出动画有效,组件会一瞬间显示;
综合上面的情况,加上本人在项目中实践(如vue-router写在App.vue中):
<router-view v-slot="{ Component }">
<transition name="aside-right" appear>
<!-- 加入keep-alive保证进入退出都有动画 -->
<!-- exclude排除缓存的组件,让该detail组件不缓存数据 -->
<keep-alive exclude="detail">
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
这里detail是组件文件名,如果组件名是singer-detail.vue,那么这里就填写:
<keep-alive exclude="singer-detail">
从右边进入/退出的样式
.aside-right-enter-active,
.aside-right-leave-active {
transition: all 0.5s ease;
}
.aside-right-enter-from,
.aside-right-leave-to {
transform: translate(100%, 0);
}
关于如何配置vue-router4 请见另一篇文章 vue-router4使用
当然也可以使用路由元信息定义每个组件的过度动画:
<!-- 这里Component就是指的router中注册的组件, route就是每个路由配置的数组routes -->
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition" appear>
<!-- 加入keep-alive保证进入退出都有动画 -->
<!-- exclude排除缓存的组件,让detail组件不缓存数据 -->
<keep-alive exclude="detail">
<component :is="Component" @closeDetail=".." :all="传入参数"/>
</keep-alive>
</transition>
</router-view>
实现仿原生APP页面过度效果:左侧页面向右滑入,底部页面向左偏移
让我们放慢动画执行时间,先看一下,当路由切换时发生了什么:
.aside-right-enter-from,
.aside-right-leave-to {
// 进入和退出时候,底部的页面(组件执行的动画)
&.main-list{
transform: translate(-10%, 0);
}
// 当执行动画时 对主页和详细页区别对待
&.detail{
transform: translate(100%, 0);
}
}
(补充)进入动画生效,移除动画无效
我还遇到一种情况,就是进入时候动画生效,路由返回的时候动画无效,但是在路由上加上keep-alive(缓存)就会生效,效果表现是直接将节点元素删除而不执行动画的情况。具体效果如下(如查看不了,请点击此处的备用链接):
可以看到,进入的时候动画正常,但是退出的时候,瞬间将singer-detail的DOM节点删除了,导致动画不生效。奇怪的是,如果在路由上加上keep-alive,动画会正常进入退出。
singer组件:
<router-view v-slot="{ Component }">
<transition appear name="slide">
<!--
仅仅加上<keep-alive>缓存结果就会正常执行进入、退出动画
如果一旦加上exclude或者不加keep-alive就会直接删除DOM不执行退出动画
-->
<keep-alive exclude="singerDetail">
<component :is="Component"
:singer="selectedSinger"
></component>
</keep-alive>
</transition>
</router-view>
下面来看一下这个问题产生的原因,很奇妙,竟然跟一句注释有关,先来看一下组件之间的关联:
{
path: '/singer',
component: Singer,
children: [
{
path: ':id',
component: SingerDetail
}
]
},
singer组件(父组件)
<router-view v-slot="{ Component }">
<transition appear name="slide">
<!-- <keep-alive exclude="singerDetail">
保留或者去掉切换组件时要重执行created()
-->
<component :is="Component"
:singer="selectedSinger"
></component>
<!-- </keep-alive>-->
</transition>
</router-view>
singerDetail组件:
<template>
<!-- 因为通过二级路由实现,所以放在views下 注意!就是这条注释!!不要在这个位置加任何注释! -->
<section class="singer-detail">
<music-list
:songs="songs"
:title="title"
:pic="pic"
:loading="loading"
></music-list>
</section>
</template>
这里在singer-detail组件中,又引入了一个Music-list组件。我们可以发现 在最外层section上面有一条注释!造成退出动画失效的元凶就是它!!如果大家像我一样喜欢加注释,不要在template的第一层加注释!
<template>
<section class="singer-detail">
<!-- 因为通过二级路由实现,所以放在views下 这样就没问题了-->
<music-list
要像上面一样,切记不要直接在template的第一层直接加注释,会导致transition动画无效!!如果你也遇到类似问题,不妨检查一下这个点。感谢ustb黄轶老师。
具体原因先留个坑,以后再更新。
关于keep-alive
<keep-alive>
是Vue的一个内部组件,适合用来缓存不需要实时更新的组件,这样可以保留组件状态避免重新渲染。keep-alive是一个缓存的机制,keep-alive要配合router-view使用
参数:
- include :接受字符串或正则表达式,这里是需要被缓存的组件名
- exclude :接受字符串或正则表达式,这里是不需要缓存的组件名
- max :接受数字,最多可以缓存多少组件实例
使用keep-alive后,会导致一个问题比如详细页,是需要根据请求的ID重新获取数据的,而keep-alive会让组件保存状态,在需要重新请求数据的时候,依然走的缓存。从而导致了数据的不更新
这时候就需要使用include或者exclude参数,根据定义路由时候,其中组件的name,进行筛选
keep-alive缓存时include中的名字必须与组件上的名字完全一致,组件没有写名字或者名字不一致就会导致缓存失效,每次进入组件都触发created生命周期
// 全局组件中
<keep-alive include="history">
<router-view></router-view>
</keep-alive>
组件中
export default {
name: "history",/*此处的name必须有且与include中的一致*/
components: {
VTable
},
include使该标签作用于所有name属性的值跟此标签 include的属性值一致的vue页面
exclude使该标签不作用于所有name属性的值跟此标签 exclude的属性值一致的vue页面
使用include/exclude 属性需要给所有vue类的name赋值(注意不是给route的name赋值),否则 include/exclude不生效
比如:
export default {
name:'a', // include 或 exclude所使用的name
data () {
return{
}
},
}
// 保持 name为a和b的组件
<keep-alive include="a,b">
<router-view/> vue2.0写法
</keep-alive>
vue2.0版本后,keep-alive内置组件已经封装了两个属性,include和exclude表示那些组件需要缓存那些组件不需要缓存,用法大致如下:
<keep-alive include="test-keep-alive">
<!-- 将缓存name为test-keep-alive的组件 -->
<component></component>
</keep-alive>
<keep-alive include="a,b">
<!-- 将缓存name为a或者b的组件,结合动态组件使用 -->
<component :is="view"></component>
</keep-alive>
<!-- 使用正则表达式,需使用v-bind -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 动态判断 -->
<keep-alive :include="includedComponents">
<router-view></router-view>
</keep-alive>
<keep-alive exclude="test-keep-alive">
<!-- 将不缓存name为test-keep-alive的组件 -->
<component></component>
</keep-alive>
路由内组件派发事件的监听以及传递值
<router-view v-slot="{ Component }">
<transition name="aside-right" appear>
<!-- 加入keep-alive保证进入退出都有动画 -->
<!-- exclude排除缓存,让其不缓存数据 -->
<keep-alive exclude="detail">
<!-- 监听组件内派发的closeDetail事件 -->
<component :is="Component" @closeDetail="indexShareConfig" :all="要传递的数据"/>
</keep-alive>
</transition>
</router-view>
detail.vue组件
methods: {
closeDetail() {
// 关闭时候向外发送事件
this.$emit('closeDetail');
},
}