目录
1. 效果图
2. 思路分析
2.1 实现思路
2.2 可能存在的问题
2.2.1 并发请求管理
2.2.2 请求快速响应和缓存带来的问题
3. 代码实现
4. 总结
1. 效果图
如下图所示,当路由变化或发起请求时,出现 Loading 等待效果,此时页面不可见。当路由跳转结束或请求结束时,Loading效果隐藏,页面可见。
2. 思路分析
接下来从思路和难点入手分析,最终代码实现,解决问题。
2.1 实现思路
(1) 需要有一个 Loading.vue 组件,可以自己写,也可以用组件库中的组件。
(2) 创建 vuex,通过 vuex store 来管理一个 loading 状态。
(3) 需要在 store 中定义一个计数器,用来追踪正在请求的数量。
(4) 使用 Axios 拦截器,在每次请求开始的时候增加计数器,在请求完成的时候减少计数器。
(5) 根据计数器的值来控制 loading 状态,进而控制 Loading.vue 组件的显示隐藏。
2.2 可能存在的问题
2.2.1 并发请求管理
描述:当应用程序中有多个并发请求时,简单的开始和结束 loading 状态的方式可能会导致 loading 状态提前结束或不正确地显示。例如,如果在 endloading 被调用时还有其他请求未完成,loading 状态应该保持为 true。
参考方法:使用计数器追踪请求数量,如上述所说,Vuex store 中引入一个计数器来追踪正在进行中的请求数量。每次发起请求时增加计数器,请求完成后减少计数器。只有当所有请求都完成后(即计数器归零),才将 loading 置为 false。
2.2.2 请求快速响应和缓存带来的问题
描述:由于网络速度较快或浏览器缓存的原因,某些请求可能会非常快地完成,导致 loading 状态几乎不显示。此外,浏览器可能会从缓存中获取资源,从而绕过了正常的请求流程,使得 loading 状态没有机会被触发。
参考方法:在 endLoading 中引入一个最小延迟(我设置的 300 毫秒)。这样即使请求很快完成,loading 指示器也会显示足够长的时间让用户注意到。可以使用 setTimeOut 来实现这一功能。
3. 代码实现
(1) 如果没有就先下载 Axios 和 vuex。(我是在创建项目的时候就已经下载好了)
下载命令
npm install vuex --save
npm install axios --save
(2) 创建 store 文件,并在其中创建 index.js 文件来定义 Vuex store。然后在主文件 main.js 中引入并使用它。
创建文件夹
/store/index.js
// 引入 Vue 和 Vuex 库
import Vue from 'vue';
import Vuex from 'vuex';
// 使用 Vuex 插件
Vue.use(Vuex);
// 创建 Vuex Store 实例
const store = new Vuex.Store({
// 定义 state,包含应用程序的状态数据
state: {
isLoading: false, // 当前是否处于加载状态
pendingRequests: 0, // 用来追踪并发请求的数量
},
// 定义 mutations,用于同步地修改 state
mutations: {
// 增加正在处理的请求数量
incrementPendingRequests(state) {
state.pendingRequests++;
// 如果这是第一个请求,则设置加载状态为 true
if (state.pendingRequests === 1) {
state.isLoading = true;
}
},
// 减少正在处理的请求数量
decrementPendingRequests(state) {
if (state.pendingRequests > 0) {
state.pendingRequests--;
// 如果所有请求都已完成,则设置加载状态为 false
if (state.pendingRequests === 0) {
state.isLoading = false;
}
}
}
},
// 定义 actions,用于异步操作和触发 mutations
actions: {
// 开始加载状态,通过调用 mutation 增加请求数量
startLoading({ commit }) {
commit('incrementPendingRequests');
},
// 结束加载状态,通过调用 mutation 减少请求数量
endLoading({ commit }) {
commit('decrementPendingRequests');
},
// 延迟结束加载状态,在指定时间后调用 endLoading action
delayedEndLoading({ dispatch }, delay = 0) {
setTimeout(() => {
dispatch('endLoading');
}, delay); // 默认延迟时间为 0 毫秒
}
},
// 定义 getters,用于从 state 中派生出一些状态
getters: {
// 获取当前的加载状态
isLoading: state => state.isLoading
}
});
// 导出 store 实例,以便在应用程序中使用
export default store;
详细注释已在代码中呈现 ↑ ↑ ↑
main.js 文件引入
(3) 创建 axios 文件夹以及相关文件(/api/apiClient.js),添加相应拦截器和请求拦截器,在这里面下文章。
创建文件夹
apiClient.js 文件
// 引入 Axios 库和 Vuex store 实例
import axios from 'axios';
import store from '../store';
// 创建自定义 Axios 实例,用于发起 HTTP 请求
const apiClient = axios.create({
baseURL: process.env.VUE_APP_API_URL, // 使用环境变量设置基础 URL,方便跨环境配置
timeout: 1000, // 设置请求超时时间为 1 秒
headers: { 'Content-Type': 'application/json' } // 设置默认请求头为 JSON 格式
});
// 添加请求拦截器,拦截所有发出的请求
apiClient.interceptors.request.use(
config => {
// 每次请求开始时,调用 Vuex store 的 startLoading action 开始加载状态
store.dispatch('startLoading');
// 返回配置对象,允许请求继续进行
return config;
},
error => {
// 如果请求在发送前发生错误(例如网络错误),调用 endLoading 结束加载状态
store.dispatch('endLoading');
// 返回一个被拒绝的 Promise,以便处理错误
return Promise.reject(error);
}
);
// 添加响应拦截器,拦截所有接收到的响应
apiClient.interceptors.response.use(
response => {
// 成功接收到响应后,调用 delayedEndLoading action,在延迟 0.5 秒后结束加载状态
store.dispatch('delayedEndLoading', 500); // 成功响应后延迟 0.5 秒结束 loading
// 返回响应对象,允许后续处理
return response;
},
error => {
// 如果响应失败(例如服务器返回错误码),立即调用 endLoading 结束加载状态
store.dispatch('endLoading');
// 返回一个被拒绝的 Promise,以便处理错误
return Promise.reject(error);
}
);
// 导出自定义 Axios 实例,以便在整个应用程序中使用
export default apiClient;
详细解说见代码注释 ↑ ↑ ↑
(4) 自定义一个 Loading 组件,代码如下。
Loading.vue 文件
<template>
<!-- 使用 v-if 指令根据 isLoading 状态显示或隐藏加载指示器 -->
<div v-if="isLoading" class='base'>
<!-- 加载动画图片,当 isLoading 为 true 时显示 -->
<img src="../assets/images/preloader.gif" alt="Loading...">
</div>
</template>
<script>
export default {
name: 'Loading',
data() {
return {
}
},
mounted() {
},
computed: {
isLoading() {
// 通过 this.$store.getters 访问 Vuex store 的 getters,
// 并返回 isLoading 状态,以控制加载指示器的显示与否
return this.$store.getters.isLoading;
}
}
}
</script>
<style scoped>
body {
background-color: white;
font-size: 12px;
}
/* 加载指示器容器样式 */
.base {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
}
</style>
(5) 实现路由跳转时 loading 效果的呈现。
只需在 /router/index.js 中添加如下代码
// 全局前置守卫
router.beforeEach((to, from, next) => {
// 开始加载状态
store.dispatch('startLoading');
// 继续导航
next();
});
// 后置钩子
router.afterEach(() => {
// 这里可以设置一个短暂的延迟来模拟loading效果,或者直接结束loading
setTimeout(() => {
store.dispatch('endLoading');
}, 300); // 可选的延迟时间
});
(6) 在 App.vue 入口文件,放置 Loading.vue组件,当 store 中的 loading 为 true 时,就用Loading.vue组件遮住页面,达到加载中的效果。
App.vue文件
<template>
<div id="app" :class="{ 'is-loading': $store.getters.isLoading }">
<router-view />
<Loading />
</div>
</template>
<script>
import Loading from './components/Loading.vue'
export default {
name: 'App',
components: {
Loading,
}
}
</script>
<style>
*{margin: 0;padding: 0;}
li{list-style:none}
a{text-decoration:none}
#app{
height: 100vh;
}
#app.is-loading::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: white;
z-index: 9998; /* 确保它在所有元素之上但低于loading组件 */
pointer-events: none; /* 不阻止点击事件传递到下面的loading组件 */
}
</style>
注意:
Loading.vue组件需要将页面遮住,需要提一下的就是层级的问题注意一下。
(7) 然后在页面请求接口或则路由跳转时,就会出现 loading 效果
4. 总结
最主要的就是vuex的store中的部分逻辑,以及相应拦截器和请求拦截器调用store中的,总结一下实现步骤吧。(1) 引入必要的库 (2) 创建 Vuex Store (3) 配置 Axios 实例 (4) 创建加载组件 (5) 将组件集成到应用
如果以上内容对你有用的话不妨点赞、关注+收藏,防止下次迷路😀。