项目上遇到一个这样的需求,就是在打开含有iframe界面时,去切换标签,切回含有iframe界面的时候,要求iframe界面不刷新。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节点数据的效果。 具体该怎么操作呢?请继续往下看: 第一步:我们先需要修改一下路由配置,如下:
{
path: '/printReportShow',
component: Layout,
redirect: '/printReportShow',
hidden: false,
name: 'Layout',
children: [
{
path: '/printReportShow',
iframeComponent: printReportShow,
name: 'printReportShow',
hidden: false,
meta: {title: '打印预览',}
}
]
},
从以上代码,不难看出/printReportShow就是我们要跳转的路由地址,我们可以看到printReportShow的路由配置中组件我写的是iframeComponent而非官方教程中的component,这样造成的后果就是在 router-view 节点中渲染时,页面显示的是空白页。 第二步:修改主界面AppMain.vue(你的可能不叫这个)中的template,添加vue的内置组件component,如下:
<template>
<section class="app-main">
<transition name="move" mode="out-in" @enter="onEnter">
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
</transition>
<!--iframe页-->
<component
v-for="item in hasOpenComponentsArr"
:key="item.children[0].name"
:is="item.children[0].name"
v-show="$route.path === item.path"
></component>
</section>
</template>
从以上代码我们可以看到,iframe节点数据在根节点App.vue渲染时,也随即跟着渲染了,但是考虑到性能原因,对此我们选择将iframe的显示做成懒加载形式的,只有在用户进入相应的页面时才触发渲染,在渲染完毕后再通过v-show去控制界面在切换时的显示与隐藏。 第三步:AppMain.vue中具体的逻辑处理
<script>
import Vue from 'vue';
export default {
name: 'AppMain',
computed: {
// 实现懒加载,只渲染已经打开过(hasOpen:true)的iframe页
hasOpenComponentsArr() {
return this.componentsArr.filter(item => {
return item.children[0].hasOpen
});
}
},
data() {
return {
componentsArr: []
}
},
watch: {
$route() {
// 判断当前路由是否iframe页
this.isOpenIframePage();
}
},
created() {
// 设置iframe页的数组对象
const componentsArr = this.getComponentsArr();
componentsArr.forEach((item) => {
// Vue.component(item.name, item.component);
Vue.component(item.children[0].name, item.children[0].component);
});
this.componentsArr = componentsArr;
// 判断当前路由是否iframe页
this.isOpenIframePage();
},
methods:{
// 根据当前路由设置hasOpen
isOpenIframePage() {
const target = this.componentsArr.find(item => {
return item.path === this.$route.path
});
if (target && !target.children[0].hasOpen) {
target.children[0].hasOpen = true;
}
},
// 遍历路由的所有页面,把含有iframeComponent标识的收集起来
getComponentsArr() {
const router = this.$router;
const routes = router.options.routes;
const iframeArr = []
const singleRoutes = routes[6];
const printReportObj = {
name: routes[6].children[0].name,
path: routes[6].children[0].path,
hasOpen: false, // 是否打开过,默认false
component: routes[6].children[0].iframeComponent // 组件文件的引用
}
singleRoutes.children.splice(0,1);
singleRoutes.children.push(printReportObj);
iframeArr.push(singleRoutes);
return iframeArr
}
}
}
</script>
备注:由于我们系统中就涉及到一个iframe界面,所以在处理上可能有些地方写死了,如果是多iframe的可以自行参考着做相应的修改。 4、拓展
由于切换标签,含iframe的component组件不会再触发路由钩子函数和生命周期函数,导致界面数据无法做更新操作,同时浏览器刷新时,界面数据会有丢失的问题,所以我们可以考虑去监听sessionStorage的值,以此来更新数据。详情可以看下面这篇文章:vue中如何实现对sessionStorage的监听。
4.vue中如何实现对sessionStorage的监听
1、使用场景
(1)在vue项目中,为了在浏览器刷新时,页面数据不丢失,我们会选择将数据浏览器缓存sessionStorage中。 (2)为了传递参数 由于切换标签,含iframe的组件不会在触发路由钩子函数和生命周期函数,导致界面数据无法做更新操作,同时还要解决浏览器刷新时,界面数据不丢失的问题,所以才考虑到去监听sessionStorage的值,以此来更新数据。
2、具体实现思路
(1)在vue项目main.js文件中,在vue原型上定义resetSetItem,代码如下:
Vue.prototype.resetSetItem = function (key, newVal) {
// 创建一个StorageEvent事件
var newStorageEvent = document.createEvent('StorageEvent');
const storage = {
setItem: function (k, val) {
sessionStorage.setItem(k, val);
// 初始化创建的事件
newStorageEvent.initStorageEvent(key, false, false, k, null, val, null, null);
// 派发对象
window.dispatchEvent(newStorageEvent)
}
}
return storage.setItem(key, newVal);
}
2)通过resetSetItem方法存值,代码如下:
self.resetSetItem('printReportShowQuery', JSON.stringify(query));
3)getItem取值和window.addEventListener监听,代码如下:
created() {
let routQuery = {};
let self = this;
// ***为resetSetItem函数对应的第一个key参数
window.addEventListener('***', ()=> {
let printReportShowQuery = JSON.parse(sessionStorage.getItem('printReportShowQuery'));
routQuery.url = printReportShowQuery.url;
routQuery.params = JSON.parse(printReportShowQuery.params);
self.init(routQuery);
});
},