VueRouter 后退页面刷新问题解决方案

用 VueRouter hash 模式时,点击浏览器上的后退按钮,浏览器显示上一次浏览页面的时候,会重新执行页面,像页面刷新一样。

跟微信小程序不一样,微信小程序后退的时候不会重新执行,只是给我们一个 onShow 事件,这样如果需要刷新数据,我们就可以在 onShow 里面解决。

解决方案1:用 keep-alive 组件

使用 Vue 内置的 keep-alive 组件。
使用该组件后,可缓存组件状态, 页面二次显示时,触发 onActivated 事件,我们可在 onActivated 中决定是否刷新列表数据。
此方法解决比较完美,但是稍微复杂,并发性能要求较高时,推荐使用该方案。

第1步,修改 router-view 标签

<router-view></router-view> 改为:

1
2
3
4
5
6
<router-view v-slot="{ Component, route }">
<keep-alive>
<component :is="Component" v-if="route.meta.keepAlive" />
</keep-alive>
<component :is="Component" v-if="!route.meta.keepAlive" />
</router-view>

keep-alive 需要注意的是:组件第一次打开加载数据渲染页面,第二次打开时,不管你是点击链接还是前进后退到该组件,都是触发 onActivatedonDeactivated 事件,而不会重新执行&渲染页面,因此需要手动检测数据更新时重新加载数据。

第2步,路由设置

在需要保持状态的页面的路由上加上 meta.keepAlive = true 参数,如

1
{ path: '/orders', component: () => import('./pages/order/Index.vue'), meta: { keepAlive: true } }

第3步,数据有更新则刷新列表数据

详情页更改后,标记数据已更改,然后再在列表页检测数据是否变更,变更则刷新数据。

1
2
3
4
5
6
7
8
<!-- pages/order/Index.vue -->
<script setup>
import { onActivated } from 'vue';
// 加上 keep-alive 组件后,路由组件增加 activated 事件,页面显示时触发。
onActivated(() => {
// 检测列表数据否已更改,更改则刷新数据
});
</script>

解决方案2:把请求参数同步到 hash 后的查询参数

并发要求不高的可用此方法,简单。

修改 hash 的查询串,页面不会被刷新。如果列表页有请求参数变化时,可修改 URL hash,把请求参数替换到 hash 后面的查询串,页面加载时根据请求参数请求服务器数据。这样就完全不用检查列表数据是否已更新,还能保证数据实时性,唯一美中不足的是,后退还要重新请求服务器数据、重新渲染页面。

1
2
3
4
5
6
7
8
9
10
11
axios.get('/admin/users', query, (resp) => {
// 更新列表数据
Object.assign(data, resp.data);

// 请求参数转换成查询串字符串
const queryString = new URLSearchParams(query).toString();
// 取得去掉查询串后的 URL hash
const hash = window.location.hash.replace(/(.*?)\?.*/, '$1');
// 更新 URL hash 后面的查询串
window.location.hash = hash + '?' + queryString;
});

解决方案3:把子页面封装成模态窗子组件

列表页的新建、编辑、查看详情可做成 模态窗 方式,不存在后退到列表页的情况,自然就不用担心页面重新执行的问题,而且用户体验还更好。
如果表单页面比较复杂,再使用前面的方案。

注意:数据更新后注意刷新列表数据。新建、编辑后都需要刷新列表数据,因此建议在父组件(列表页)暴露刷新数据的方法,再在新建、编辑组件中回调。

实现1 使用 expose

在子组件的 setup 中用 getCurrentInstance().parent.exposed 可以访问父组件用 defineExpose 暴露的绑定。

编辑用户组件(页面):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!-- user/Edit.vue -->
<script setup>
import { ref, reactive, getCurrentInstance } from 'vue';

const parentExposed = getCurrentInstance().parent.exposed; // 父组件暴露的绑定
const isShow = ref(false);
const user = reactive({});

const onClose = () => {
isShow.value = false;
};

const show = (id) => {
isShow.value = true
// 根据 ID 获取远程数据 ...
Object.assign(user, 服务器返回数据);
}

const save = () => {
// 提交数据 ...
// 更新列表数据
parentExposed.reloadData();
}

// 暴漏 show 方法
defineExpose({ show });
</script>

<template>
<el-dialog v-model="isShow" title="编辑" @close="onClose">
{{ user.id }}
...
<input v-model="item.name" />
<button @click="save">保存</button>
</el-dialog>
</template>

列表组件(父),引用编辑用户信息组件(子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- user/Index.vue -->
<script setup>
import { ref } from 'vue';
import Edit from './Edit.vue';

const editRef = ref(); // Edit 组件的模板引用实例
const users = [
{id: 1, name: '老王'},
{id: 2, name: '老李'},
];

const reloadData = () => {
// 重新加载数据
}

defineExpose({ reloadData }); // reloadData 暴露出来,给子组件调用
</script>

<template>
<!-- 引用子组件 -->
<Edit ref="editRef" />
<ul>
<li v-for="user in users">
{{ user.name }}
<!-- 父组件调用 Edit 子组件的模板实例暴露的 show() -->
<button @click="editRef.show(user.id)">编辑</button>
</li>
</ul>
</template>

实现2:使用依赖注入

方案3的实现方案也可以用 依赖注入 实现,在父组件使用 provide 提供依赖,在子组件用 inject 注入上层组件提供的数据或方法。

setup 中访问父组件,使用依赖注入更便捷。在模板中访问上层组件,用 expose 更方便

1
2
3
4
5
6
7
8
<script setup>
// 子组件 Foo.vue
import { inject } from 'vue';
const xyz = inject('abc');
onMounted(() => {
xyz();// 子组件执行父组件注入的 'abc'
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 父组件 -->
<template>
<Foo />
</template>

<script setup>
import { provide } from 'vue'
import Foo from './Foo.vue';

provide('abc', () => {
console.log('Hello')
})
</script>
作者

CoderPan

发布于

2023-01-31

更新于

2024-05-08

许可协议

评论