聊一聊页面加载慢的那些事儿

847 阅读2分钟

背景

前后端分离的项目,vue + springboot,老大觉得某个页面加载太慢了,于是上手开始了优化。

前端部分

组件按需引用

不用在 main.js 中直接引用所有的组件,比如:如果项目中只有组件A需要使用到 echarts, 那就再A组件中去 import echarts 即可。如下所示:

import * as echarts from 'echarts';
export default {
    name: 'componnet-A',
    data() {
        return {}
    },
    methods: {},
    beforeMount() {},
    created() {}
};

路由懒加载

不要路由配置文件中,一上来就 import 所有的组件,而是使用懒加载的方式,如下所示:

import Vue from 'vue';
import Router from 'vue-router';
 
// 使用路由.
Vue.use(Router);

// 导出路由模块.
const router = new Router({
    mode: 'history',
    routes: [
        {
            path: '/login',
            name: 'login',
            component: resolve => require(['@/pages/login/index.vue'], resolve)
        },
        ...
    ]
});

cdn引入

在项目中,一般使用 npm 拉取包使用,但是有时因为文件过大,加载慢等原因,可以在 index.html 中使用 cdn 引入外部资源,比如使用 cdn 的方式引入 element-ui

<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

gzip打包

config-index.js 中开启 gzip 配置,如下所示:

    productionGzip: true,
    productionGzipExtensions: ['js', 'css'],

build-webpack.prod.conf.js 中增加如下配置:

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

针对具体的页面使用懒加载

生产环境上,使用的是 elementtable 展示数据,后端一次性返回了500条数据,由于业务需求,页面上没有做分页处理,一次性加载500条数据的时间确实比较长。因此考虑到页面懒加载。 然而问题来了,如果每次懒加载的数据从后端获取,其实也不太好,因为每次请求都会消耗时间。 所以最终的方案是: 一次请求返回所有数据,前端每次只展示30条数据,滚动条滚动到底部时再依次加载下一个30条数据。 代码如下:

import axios from 'axios';
export default {
    name: 'componnet-A',
    data() {
        return {
            // 当前展示第几页.
            pageIndex: 0,
            // 总共有多少页.
            pageTotal: 0,
            // 每页展示多少条数据.
            pageSize: 30,
            // 页面上展示的数据.
            dataList: [],
            // 后端返回的所有数据.
            totalDataList: []
        }
    },
    methods: {
        loadData() {
            // axios一次性请求所有需要数据.
             axios({
                    method: 'get',
                    url: 'http://xxxxx:8080',
                }).then(response => {
                    if (response.data) {
                       return;
                    }
                    this.totalDataList = response.data;
                    this.dataList = this.dealTableData();
                })
                .catch(error => {
                    this.$message.error(error);
                });
        },
        // 根据pageIndex的值获取此时应该展示哪个范围内的数据.
        dealTableData() {
            // 向上取整计算出总页数.
            this.totalPage = Math.ceil(this.totalDataList.length / 30);
            // 获取需要再展示的开始页和结束页.
            const beginPage = this.pageIndex * this.pageSize;
            const endPage = (this.pageIndex + 1) * this.pageSize - 1;
            // 返回需要再展示的具体的数据.
            return this.totalDataList.slice(beginPage, endPage);
        },
    },
    mounted() {
        const _this = this;
        // 监听滚动条的滚动事件,根据当前的indexPage计算出要不要加载下一页数据.
        this.$nextTick(() => {
            // 这里我取的是表格内的body-wrapper,可以根据自己的实际情况获取dom元素.
            const dom = document.querySelector('.el-table__body-wrapper');
            dom.addEventListener('scroll', (v) => {
                const scrollDistance = dom.scrollHeight - dom.scrollTop - dom.clientHeight;
                if (scrollDistance <= 0) {
                    // 当前页数小于总页数就请求.
                    if (_this.pageIndex < _this.totalPage) {
                    _this.pageIndex++;
                    // 当前页数自增.
                    _this.showTableData = _this.showTableData.concat(_this.dealTableData());
                    }
                }
            });
        });
    },
    created() {
        // 加载初始数据.
        this.loadData();

    }
};

后端部分

代码逻辑优化

复查代码逻辑,发现有缺少经验的同事在代码中写了多层循环,且有循环查询数据库。最终将代码逻辑改为只查一次数据库,且只有一层循环,设计到具体的代码,这里就不占出来了。

sql优化

查看sql的执行时间,发现较慢,于是给相关字段加上了索引。

gzip配置

  1. application.yml 中增加 gzip 配置:
server:
  shutdown: graceful # 优雅停机
  compression:
    enabled: true
    min-response-size: 1024
    mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
  1. nginx 的配置文件中开启 gzip :
gzip  on;
gzip_types text/plain application/x-javascript application/javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;