目的:vue-cli构建的vue单页面应用,某些特定的页面,实现前进刷新,后退不刷新,相似app般的用户体验。
注: 此处的刷新特指当进入此页面时,触发ajax请求,向服务器获取数据。不刷新特指当进入此页面时,不触发ajax请求,而是使用以前缓存的数据,以便减小服务器请求,用户体验更流畅。html
任何技术的探索,都来自项目的需求。以前经手的一个项目是微信端商城,使用的是传统的mvc模式,利用的是jq+js,所以对于商城的项目需求比较熟悉。目前在学习vue,练手一个商城,遇到以前常常说起而没法很好解决的需求。有些页面须要前进刷新,后退不刷新。好比,从商城的【首页】-->【详情页】-->【订单提交页】,每次打开新页面都须要获取新数据,可是按下返回键后,就不须要再获取新数据了,而滚动条还保留在以前的位置。最多见的操做是从【首页】-->【详情页】,而后在从【详情页】-->【首页】,如此反复。
实例如图:vue
前人栽树,后人好乘凉。技术圈的分享一直都在蓬勃发展。遇到问题,咱们能够尽情去搜索,去寻找大佬的足迹。针对上述需求,看到一个分享vue-router 之 keep-alive,比较符合个人需求,可是使用到个人项目上发现,稍微有点不适合。此分享技术要点,比较适合两个页面以前的跳转,返回。而个人页面是多个路由(2+)之间的跳转,返回。无奈,只能去本身探索发现。不过此技术要点给了我很好的启发,特此感谢做者。@ RoamIngit
注:demo中,index页面包含三个连接导航。page1-->page2-->page3.依次前进,每次前进到一个新页面都须要获取数据,而按下后退键后,从page3返回到page2,page2再也不获取新数据,而是使用以前缓存的数据。从page2返回到page1时,page1再也不获取新数据,而是使用以前的数据。因此,page1和page2须要缓存,page3不须要缓存。能够把page1想象成首页,page2想象成详情页,page3想象成订单提交页。这样方便理解。github
在app.vue中改写router-viewajax
<keep-alive> <router-view v-if="$route.meta.keepAlive"> <!-- 这里是会被缓存的视图组件,好比 page1,page2 --> </router-view> </keep-alive> <router-view v-if="!$route.meta.keepAlive"> <!-- 这里是不被缓存的视图组件,好比 page3 --> </router-view>
在router/index.js中添加路由元信息,设置须要缓存的页面vue-router
routes: [{ path: '/', name: 'index', component: index, meta: { keepAlive: false, //此组件不须要被缓存 } }, { path: '/page1', name: 'page1', component: page1, meta: { keepAlive: true, //此组件须要被缓存 } }, { path: '/page2', name: 'page2', component: page2, meta: { keepAlive: true, // 此组件须要被缓存 } }, { path: '/page3', name: 'page3', component: page3, meta: { keepAlive: false, // 此组件不须要被缓存 } } ]
钩子函数的执行顺序vue-cli
不使用keep-alive
beforeRouteEnter --> created --> mounted --> destroyed缓存
使用keep-alive
beforeRouteEnter --> created --> mounted --> activated --> deactivated
再次进入缓存的页面,只会触发beforeRouteEnter -->activated --> deactivated 。created和mounted不会再执行。咱们能够利用不一样的钩子函数,作不一样的事。务必理解上述钩子函数的执行时机和执行顺序,本教程的核心就依赖于此钩子函数
activated和deactivated是使用keep-alive后,vue中比较重要的两个钩子函数,建议详细了解下。服务器
注:demo中的page1和page2,这两个页面都须要缓存,思路同样,如下以page1为例,page2再也不赘述。
示例文件:components/page1.vue微信
data中初始化一个str字符串,存放从后台获取的数据
data() { return { msg: "我是第一个页面", str: "" // 加载页面后执行获取数据的方法,插入到此 }; }
methods中建立一个方法,模拟从后台获取数据
methods: { getData() { // getData方法,模拟从后台请求数据 this.str = "我是经过调用方法加载的数据。。。"; } }
修改router/index.js中的配置
每次进入页面,咱们都须要知晓是从哪一个页面进来的,用以判断是否须要获取数据。以这个page1页面为例,当咱们知晓是从page2过来的,咱们就能够认为是用户操做了返回键,这时page1页面就不须要再获取新数据了,使用以前缓存的数据就能够了。若是是从别的页面过来的,咱们就须要获取数据。
咱们能够经过beforeRouteEnter这个钩子函数中的from参数判断是从哪一个页面过来的,这个参数执行时,组件实例还没建立,全部不能在data中定义变量。咱们能够在路由中定义一个变量,用来判断。
在router/index.js的meta中添加isBack变量,默认false
{ path: '/page1', name: 'page1', component: page1, meta: { keepAlive: true, //此组件须要被缓存 isBack:false, //用于判断上一个页面是哪一个 } }, { path: '/page2', name: 'page2', component: page2, meta: { keepAlive: true, // 此组件须要被缓存 isBack:false, //用于判断上一个页面是哪一个 } },
beforeRouteEnter中判断是从哪一个页面过来的
判断是从哪一个路由过来的,若是是page2过来的,代表当前页面不须要刷新获取新数据,直接用以前缓存的数据便可
beforeRouteEnter(to, from, next) { // 路由导航钩子,此时还不能获取组件实例 `this`,因此没法在data中定义变量(利用vm除外) // 参考 https://router.vuejs.org/zh-cn/advanced/navigation-guards.html // 因此,利用路由元信息中的meta字段设置变量,方便在各个位置获取。这就是为何在meta中定义isBack // 参考 https://router.vuejs.org/zh-cn/advanced/meta.html if(from.name=='page2'){ to.meta.isBack=true; //判断是从哪一个路由过来的, //若是是page2过来的,代表当前页面不须要刷新获取新数据,直接用以前缓存的数据便可 } next(); },
activated中执行getData这个获取数据的方法
由于这个页面须要缓存。只有第一次进入时才会执行created和mounted方法,再次进入就不执行了。而activated每次进入都执行,因此在这个钩子函数中获取数据。
activated() { if(!this.$route.meta.isBack){ // 若是isBack是false,代表须要获取新数据,不然就再也不请求,直接使用缓存的数据 this.getData(); } // 恢复成默认的false,避免isBack一直是true,致使下次没法获取数据 this.$route.meta.isBack=false },
这样就能够了?
当这样设置完毕后,你执行起来,貌似是能够了。第一次进入page1,能获取新数据,从page2返回时,再也不获取新数据了,而是使用以前缓存的数据。但这样还有一个问题,当用户从page1进入page2后,由于某种缘由,手动刷新了page2的页面。这时再返回到page1,发现以前缓存的数据丢失了,并且也没有再从新获取。因此咱们还须要再添加一个判断条件,当用户手动刷新页面后,再返回时就须要从新获取数据了。
如何添加这个条件,判断用户是否刷新了页面呢?咱们知道,当使用keep-alive后,只有第一次进入后会触发created钩子函数,再次进入就再也不执行了。当用户刷新了页面,这个钩子函数就会又执行,因此,咱们能够利用这个小技巧来作点文章。
data中定义变量isFirstEnter用来判断是否第一次进入,或是否刷新了页面,默认false
data() { return { msg: "我是第一个页面", str: "", // 加载页面后执行获取数据的方法,插入到此 isFirstEnter:false // 是否第一次进入,默认false }; },
created中把isFirstEnter变为true,说明是第一次进入或刷新了页面
created() { this.isFirstEnter=true; // 只有第一次进入或者刷新页面后才会执行此钩子函数 // 使用keep-alive后(2+次)进入不会再执行此钩子函数 },
activated中增长判断条件
activated() { if(!this.$route.meta.isBack || this.isFirstEnter){ // 若是isBack是false,代表须要获取新数据,不然就再也不请求,直接使用缓存的数据 // 若是isFirstEnter是true,代表是第一次进入此页面或用户刷新了页面,需获取新数据 this.str=''// 把数据清空,能够稍微避免让用户看到以前缓存的数据 this.getData(); } // 恢复成默认的false,避免isBack一直是true,致使下次没法获取数据 this.$route.meta.isBack=false // 恢复成默认的false,避免isBack一直是true,致使每次都获取新数据 this.isFirstEnter=false; },
这样应该就能够了
注:demo中的page3,这个页面不须要缓存,该怎么写就怎么写,不须要作特别的设置。
使用keep-alive后,可能有点小问题:第二个页面可能继承第一个页面的滚动条的高度。(在我项目中遇到的)
好比:page1向下滚动后,再进入page2,这时page2的滚动条多是以前的高度,可能不会在顶部。
解决方法一
每次离开记录滚动条的高度,再次进入时根据项目须要再恢复以前的高度,或者置顶。
解决方法二(推荐)
router/index.js中添加以下代码(如不理解,请看参考连接)
参考:HTML5 History 模式 滚动行为
mode: 'history', scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { x: 0, y: 0 } } }
在这次demo练习中,打印了一下钩子函数的执行顺序,发现一个疑问点(我对钩子函数理解也很浅显):
从page1进入page2时,先执行了page2的beforeRouteEnter和created方法,而后才执行page1的deactivated方法。
因此我把这两个初始化设置,放在了activated里面,而没有放在deactivated中
this.$route.meta.isBack=false; this.isFirstEnter=false;
为了解决这个前进刷新后退不刷新问题,让我整整苦恼了一周时间,想了不少方法,也没能解决。最后综合各个大佬经验,试验了不少次,才归结出这个比较‘low’的方法。
目前,我也是vue小白,也在探索着前进,若是这个方法能解决你遇到的难题,我很高兴。若是你认为的确很low,求轻喷。
demo在下方的GitHub中,欢迎star。
也欢迎你们提供意见和建议,谢谢你们