MST

星途 面试题库

面试题:Vue复杂网络请求场景下全局加载动画与提示框的性能优化及异常处理

在一个大型Vue项目中,存在大量不同类型的网络请求(如并发请求、分页请求、文件上传下载等),同时要展示全局加载动画和提示框。如何在保证动画流畅性、提示框交互性的同时优化性能?并且针对网络异常(如超时、重复请求、断网重连等)如何进行优雅的处理?请详细阐述设计思路、技术选型以及核心代码架构。
49.2万 热度难度
前端开发Vue

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 全局加载动画和提示框管理:使用Vue的插件机制,创建一个全局的加载和提示组件,通过状态管理控制其显示与隐藏,这样可以保证在不同类型请求时统一处理。
  2. 性能优化
    • 并发请求:利用Promise.all控制并发数量,避免过多请求同时发送导致性能问题。
    • 分页请求:采用防抖或节流策略,避免用户频繁触发请求。同时,在服务端做缓存优化,减少重复查询数据库的开销。
    • 文件上传下载:采用断点续传技术,对于大文件可以提高传输效率和稳定性。在前端可以限制同时上传/下载的文件数量。
  3. 网络异常处理
    • 超时处理:设置合理的超时时间,利用Promise.race结合setTimeout实现。
    • 重复请求:维护一个请求队列,每次发送请求前检查是否已有相同请求在队列中,若有则取消当前请求。
    • 断网重连:利用window.addEventListener('online', callback)监听网络恢复,自动重发未完成的请求。

技术选型

  1. 状态管理:Vuex用于管理全局加载动画和提示框的状态,以及网络请求的状态。
  2. HTTP请求库:Axios,它具有丰富的功能,如拦截器、超时设置等,方便处理各种网络请求场景。
  3. UI库:如Element - UI或Ant - Design - Vue,用于提供加载动画和提示框组件。

核心代码架构

  1. Vuex状态管理
// store.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        isLoading: false,
        loadingText: '',
        errorMessage: '',
        requestQueue: []
    },
    mutations: {
        SET_LOADING(state, payload) {
            state.isLoading = payload.isLoading;
            state.loadingText = payload.loadingText;
        },
        SET_ERROR(state, errorMessage) {
            state.errorMessage = errorMessage;
        },
        ADD_REQUEST(state, requestConfig) {
            state.requestQueue.push(requestConfig);
        },
        REMOVE_REQUEST(state, requestConfig) {
            state.requestQueue = state.requestQueue.filter(config => config!== requestConfig);
        }
    },
    actions: {
        showLoading({ commit }, payload) {
            commit('SET_LOADING', payload);
        },
        hideLoading({ commit }) {
            commit('SET_LOADING', { isLoading: false, loadingText: '' });
        },
        showError({ commit }, errorMessage) {
            commit('SET_ERROR', errorMessage);
        },
        addRequest({ commit }, requestConfig) {
            commit('ADD_REQUEST', requestConfig);
        },
        removeRequest({ commit }, requestConfig) {
            commit('REMOVE_REQUEST', requestConfig);
        }
    }
});

export default store;
  1. Axios拦截器
import axios from 'axios';
import store from './store';

// 请求拦截器
axios.interceptors.request.use(config => {
    store.dispatch('addRequest', config);
    store.dispatch('showLoading', { isLoading: true, loadingText: '正在请求数据...' });
    return config;
}, error => {
    store.dispatch('hideLoading');
    return Promise.reject(error);
});

// 响应拦截器
axios.interceptors.response.use(response => {
    store.dispatch('removeRequest', response.config);
    if (store.state.requestQueue.length === 0) {
        store.dispatch('hideLoading');
    }
    return response;
}, error => {
    store.dispatch('removeRequest', error.config || {});
    if (store.state.requestQueue.length === 0) {
        store.dispatch('hideLoading');
    }
    if (error.message.includes('timeout')) {
        store.dispatch('showError', '请求超时,请稍后重试');
    } else if (error.code === 'ECONNABORTED' && error.message.includes('Network')) {
        // 断网处理
        window.addEventListener('online', () => {
            // 重新发送请求
            const retryRequest = error.config;
            axios(retryRequest);
        });
        store.dispatch('showError', '网络连接中断,请检查网络');
    } else {
        store.dispatch('showError', '请求失败,请稍后重试');
    }
    return Promise.reject(error);
});

export default axios;
  1. 分页请求示例(结合防抖)
<template>
    <div>
        <button @click="fetchData">获取分页数据</button>
    </div>
</template>

<script>
import axios from './axios';
import { debounce } from 'lodash';

export default {
    data() {
        return {
            page: 1
        };
    },
    methods: {
        fetchData: debounce(async function() {
            try {
                const response = await axios.get(`/api/data?page=${this.page}`);
                // 处理响应数据
            } catch (error) {
                // 错误处理
            }
        }, 300)
    }
};
</script>
  1. 并发请求示例
import axios from './axios';

const request1 = axios.get('/api/data1');
const request2 = axios.get('/api/data2');

Promise.all([request1, request2])
  .then(([response1, response2]) => {
        // 处理两个请求的响应
    })
  .catch(error => {
        // 处理错误
    });
  1. 文件上传下载示例
<template>
    <div>
        <input type="file" @change="uploadFile">
        <a href="#" @click="downloadFile">下载文件</a>
    </div>
</template>

<script>
import axios from './axios';

export default {
    methods: {
        async uploadFile(event) {
            const file = event.target.files[0];
            const formData = new FormData();
            formData.append('file', file);
            try {
                await axios.post('/api/upload', formData, {
                    headers: {
                        'Content - Type':'multipart/form - data'
                    }
                });
            } catch (error) {
                // 处理上传错误
            }
        },
        async downloadFile() {
            try {
                const response = await axios.get('/api/download', {
                    responseType: 'blob'
                });
                const url = window.URL.createObjectURL(new Blob([response.data]));
                const link = document.createElement('a');
                link.href = url;
                link.setAttribute('download', 'filename');
                document.body.appendChild(link);
                link.click();
            } catch (error) {
                // 处理下载错误
            }
        }
    }
};
</script>