背景
前后端分离的项目,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
})
)
}
针对具体的页面使用懒加载
生产环境上,使用的是 element 的 table 展示数据,后端一次性返回了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配置
- 在
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
- 在
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;