Vue 项目中加入含有 iframe 的页面,同时在路由切换的过程中,要求 已加载过的iframe 的内容不会再被刷新 。Vue自带的 keep- alive 没达到预期效果
目的:为了避免每次的iframe重复加载时间过长,导致体验不好
参考链接
https://www.cnblogs.com/shj-com/p/15049030.html
https://blog.csdn.net/weixin_45677200/article/details/123892101?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-1-123892101-blog-111522544.pc_relevant_3mothn_strategy_recovery&spm=1001.2101.3001.4242.2&utm_relevant_index=4
https://www.nhooo.com/note/qa36vm.html
1、序言
最近工作中,项目上遇到一个这样的需求,去切换tab标签,切回打印预览界面的时候,要求界面不刷新。vue框架中,我们去处理此类问题,通常马上就会想到去使用vue框架中自带的keep-alive组件,所以一开始我也是去使用了keep-alive,但是发现没有达到预期效果,后面通过研究和查阅资料发现,在vue项目中加入了含有iframe的页面,在路由切换的过程中,使用keep-alive是达不到数据缓存的效果的。
2、使用keep-alive缓存不了iframe界面原因
(1)vue中的keep-alive
【1】原理:Vue 的缓存机制并不是直接存储 DOM 结构,而是将 DOM 节点抽象成了一个个 VNode节点。因此,Vue 的 keep-alive 缓存也是基于 VNode节点 而不是直接存储 DOM 节点。在需要渲染的时候从Vnode渲染到真实DOM上。
【2】参数:Keep-alive 组件提供了 include 和 exclude 两个属性,允许组件有条件的进行缓存。
include: 字符串或正则表达式。只有匹配的组件会被缓存。
exclude: 字符串或正则表达式。任何匹配的组件都不会被缓存。
【3】Keep-alive 组件提供了两个生命钩子函数,分别是 activated 和 deactivated 。
activated :当页面存在缓存的时候执行该函数。
deactivated :在页面结束时触发该方法,可清除掉滚动方法等缓存。
(2)iframe中keep-alive机制失效原因:iframe页里的内容并不属于节点的信息,所以使用keep-alive依然会重新渲染iframe内的内容。而且iframe每一次渲染就相当于打开一个新的网页窗口,即使把节点保存下来,在渲染时iframe页还是刷新的。
3、vue中实现iframe内容缓存的思路
(1)保存iframe页里的节点信息我们很难去操作,这个时候我们就该想是否能在vue的route-view节点上动些手脚。
(2)我们可以在切换不含iframe的界面时使用vue路由,在切换含iframe页的界面时利用v-show来控制显示隐藏,使iframe的节点不被删除,以此来防止界面节点被重新更新,从而达到保存iframe节点数据的效果。
具体该怎么操作呢?请继续往下看:
第一步:
修改主界面AppMain.vue(你的可能不叫这个)中的template,添加vue的内置组件component,如下:
<template>
<div id="app">
<keep-alive>
<router-view v-if="$route.meta.keepAlive">router-view>
keep-alive>
<router-view v-if="!$route.meta.keepAlive">router-view>
<component
v-for="item in hasOpenComponentsArr"
:key="item.name"
:is="item.name"
v-show="$route.path === item.path"
>component>
div>
template>
<script>
export default {
data() {
return {
componentsArr: [], // 含有iframe的页面
iframeArr: []
}
},
watch: {
$route() {
// 判断当前路由是否iframe页
this.isOpenIframePage();
}
},
created() {
var style = localStorage.getItem("style");
if(style){
document.getElementById('themeStyle').setAttribute("href",`/public/static/themes/${style}/index.css`); //实现将主题保存在内存中刷新浏览器不改变
}
//===========
// 设置iframe页的数组对象
const componentsArr = this.getComponentsArr();
componentsArr.forEach((item) => {
Vue.component(item.name, item.component);
});
console.log("componentsArr(created)",componentsArr);
this.componentsArr = componentsArr;
// 判断当前路由是否iframe页
this.isOpenIframePage();
},
computed: {
// 实现懒加载,只渲染已经打开过(hasOpen:true)的iframe页
hasOpenComponentsArr() {
console.log("computed()");
console.log(this.componentsArr);
return this.componentsArr.filter(item => item.hasOpen);
}
},
mounted(){
// 关闭浏览器窗口的时候清空浏览器缓存在localStorage的数据
window.onbeforeunload = function (e) {
var storage = window.localStorage;
//以下为了避免不退出登录就切换账号,所以当页面出现刷新的操作,也要去清掉权限组件中的缓存
localStorage.removeItem("selectqhQX")
localStorage.removeItem("selectqhQXqy")
localStorage.removeItem("selectqhQXdt")
}
},
methods:{ // 根据当前路由设置hasOpen
isOpenIframePage() {
console.log("isOpenIframePage");
const target = this.componentsArr.find(item => {
return item.path === this.$route.path
});
if (target && !target.hasOpen) {
target.hasOpen = true;
}
},
getIframe(arr) {
arr.map(item=>{
if(item.iframeComponent){
this.iframeArr.push(item);
}
if(item.children && item.children.length>0) {
this.getIframe(item.children)
}
})
},
// 遍历路由的所有页面,把含有iframeComponent标识的收集起来
getComponentsArr() {
const router = this.$router;
const routes = router.options.routes;
this.getIframe(routes);
return this.iframeArr.map((item) => {
const name = item.name || item.path.replace('/', '');
return {
name: name,
path: item.path,
hasOpen: false, // 是否打开过,默认false
component: item.iframeComponent // 组件文件的引用
};
});
}
}
}
script>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
style>
该组件主要做的是根据main.js里的routes生成一个只含有iframe页的数组对象。
watch 上监听 route,判断当前页面 在 iframe 页列表里的话就设置 hasOpen 属 性为 true ,渲染该组件用v−show=“route.path === item.path” 切换iframe页的显示与隐藏
第二步:
修改路由配置文件
import Vue from 'vue'
// 引入vue-router
import Router from 'vue-router'
import axios from 'axios'
import shareUrlArr from './share';
import SiderAndCenter from '@/framework/components/SiderAndCenter'
import Gcjs from '@/views/system/release/gcjs/index';
import {getCookie,setInof} from '@/utils/Utils';
//第三方库需要use一下才能用
Vue.use(Router);
const router = new Router({
mode: 'history', // 采用history模式URL的路径才跟配置的对应上,不然URL是先加/#再追加配置的地
routes: [
{
path:'/sharevue',
name: '本地菜单',
component:()=>import("../shareframework/ShareFramework.vue"),
redirect:"/sharevue/newztgl",
children:[
{
name: "A页面",
path: "/sharevue/mt",
iframeComponent:"/sharevue/mt",//用于标识该页面是否含有iframe页面;这个值和path值是一样的
component: () => import("../views/newWindow/p12/index.vue"),
meta: {
keepAlive: true, //此组件需要被缓存
}
},
{
name: "B页面",
path: "/sharevue/mtDetails",
iframeComponent:"/sharevue/mtDetails",//用于标识该页面是否含有iframe页面;这个值和path值是一样的
component: () => import("../views/newWindow/p12/indexDetails.vue")
},
{
name: "C页面",
path: "/sharevue/phonesyNew",
component: () => import("../views/newWindow/p13/index.vue")
},
{
name: "D页面",
path: "/sharevue/phoneywlNew",
component: () => import("../views/newWindow/p13/indexywlfx.vue"),
meta: {
keepAlive: true, //此组件需要被缓存
}
},
]
},
...shareUrlArr
]
});
//push
const VueRouterPush = Router.prototype.push
Router.prototype.push = function push (to) {
return VueRouterPush.call(this, to).catch(err => err)
}
//replace
const VueRouterReplace = Router.prototype.replace
Router.prototype.replace = function replace (to) {
return VueRouterReplace.call(this, to).catch(err => err)
}
const ReleaseRoute = ['/','/login','/ssologin','/404','/500','/test1'];
// 路由守卫
router.beforeEach((to, from, next) => {
});
export default router;
3.拓展
由于切换标签,含iframe的component组件不会再触发路由钩子函数和生命周期函数,导致界面数据无法做更新操作,同时浏览器刷新时,界面数据会有丢失的问题,所以我们可以考虑去监听sessionStorage的值,以此来更新数据。详情可以看下面这篇文章:vue中如何实现对sessionStorage的监听。
keep-alive是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM;导致在组件mounted钩子中调用的刷新页面内容时,这个钩子没有被调用。
故:使用Vue组件切换过程,执行钩子activated(keep-alive组件激活时调用),而不是挂载钩子mounted。
参考以下
https://www.cnblogs.com/candy-xia/p/11425357.html
//activated和deactivated配合keep-alive标签使用!
activated () {
console.log('实例被激活时使用,用于重复激活一个实例的时候')
}
deactivated () {
console.log('实例没有被激活时')
}
//js部分--vue生命周期
beforeCreate () {
console.log('在实例初始化之前调用')
}
created () {
console.log('在实例初始化之后调用,经常用于操作数据,发起ajax请求')
}
beforeMount () {
console.log('在挂载开始之前被调用,如果是在服务器端渲染时不被调用;在这个函数里,无法获取元素')
}
mounted () {
console.log('在挂载后被调用,也不能在服务器端渲染时被调用;这个函数里,是可以获取元素,并进行操作的')
}
beforeUpdate () {
console.log('视图层数据更新前调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM')
}
updated () {
console.log('视图层数据更新后调用')
}
beforeDestroy () {
console.log('实例销毁之前调用,在被销毁的组件中进行调用;有一些操作,会在实例已经销毁的时候还在运行,这时候为了性能考虑,就在这里结束哪些操作')
}
destroyed () {
console.log('实例销毁后调用。')
}