- 最近看了知乎上的一个问题
当后端一次性丢给你10万条数据, 作为前端工程师的你,要怎么处理? --徐小夕 zhuanlan.zhihu.com/p/147178478…
于是决定实践一下用web worker实现分页,虽然我知道我这辈子可能都不会遇到10w条数据,但是万一呢
-
具体流程如下
-
后端传来的10w条数据存在this.option里面
-
算出外层wrap的最大高度maxHeight
-
由li的高度得到每页最多显示的行数dataperpage
-
由1中wrap的scrollHeight和maxHeight和目前滚动到的页数maxPageHaveData对比,看是否滚动到了下一页,如果是的话则进行第五步,postmessage
-
通过worker.postMessage,主线程将以下参数传给子线程
{ page:maxPageHaveData+1, overallData:self.option, dataperpage: listperpage }-
子线程主要,从所有的10w条数据中截取第0条到第page页的最后一条数据(如有截取后的数据还需要进一步处理,则接着进行),数据处理完后,子线程将data也是同样通过postmessage返回给主线程
-
主线程监听子线程返回的数据,使用数据
-
-
分页加载效果图如下(如果直接加载的话,我的网页是直接崩溃的)
-
代码实现如下
// main.js
// webworker的文件要在main.js里面引入新建一个全局worker,组件需要用到该worker在组件内引入这个worker
var worker = new Worker("task.js");
export { worker }
import axios from 'axios'
Vue.prototype.$axios = axios;
new Vue({
el:"#app",
name:"container",
render:h=>h(App)
})
<!--App.vue-->
<template>
<div>
<el-select v-model="value" placeholder="请选择"
:disabled="!options||!options.length"
@visible-change="visiableChange">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<!--<el-select v-model="value" placeholder="请选择10000" :disabled="!options||!options.length">-->
<!--<el-option-->
<!--v-for="item in option"-->
<!--:key="item.value"-->
<!--:label="item.label"-->
<!--:value="item.value">-->
<!--</el-option>-->
<!--</el-select>-->
</div>
</template>
<script>
import {worker} from './main.js'
//初始化已经加载数据的页数
var maxPageHaveData = 0
export default {
data() {
return {
option:[],
options: [],
value: ''
}
},
methods:{
visiableChange(){
// 在每次下拉框出现是为select注册scroll方法
this.$nextTick(function () {
var self = this
var el_select_dropdown__wrap = document.querySelector(".el-select-dropdown__wrap")
//定义最大高度
el_select_dropdown__wrap.style.maxHeight = "274px"
//定义每一个li的高度
var lis = document.querySelectorAll('.el-select-dropdown__item')
var hei = lis[0].clientHeight
var maxPageHeight = parseFloat(el_select_dropdown__wrap.style.maxHeight)
//由wrap的最大高度可以知道每页最多显示的条数
var listperpage = Math.ceil(maxPageHeight / hei)
el_select_dropdown__wrap.addEventListener("scroll",function(){
//如果scrollHeight超过已有页数的高度,需要发送新的页码给worker,再次加载下一页的数据
if(this.scrollTop>=(maxPageHaveData)*maxPageHeight){
worker.postMessage(
{
page:maxPageHaveData+1,
overallData:self.option,
dataperpage: listperpage
}
);
maxPageHaveData++,
worker.onmessage=function(message){
self.options = message.data
};
worker.onerror=function(error){
console.log(error)
}
}
})
})
},
getData(){
//获取数据,将所有数据传到web worker做分页处理,如果涉及计算,也是在web worker做处理
this.$axios.get('http://localhost:3000/getData').then(res=>{
this.option = res.data
worker.postMessage(
{
page:1,
overallData:res.data,
dataperpage: 10
}
);
})
}
},
created(){
this.getData()
},
mounted(){
var self = this
worker.onmessage=function(message){
self.options = message.data
};
worker.onerror=function(error){
console.log(error)
console.log(error.filename,error.lineno,error.message);
}
},
}
</script>
// task.js
onmessage = async function(message){
let overallData=message.data.overallData||[];
let page = message.data.page
let dataperpage = message.data.dataperpage
if((page+1)*message.data.dataperpag>=overallData.length){
let end = overallData.length
}else{
end = (page+1)*dataperpage
}
//worker截取下一页数据之前的所有数据
let data = overallData.slice(0,end)
//如果需要对返回结果做更多的处理,也是在worker完成
data = data.map(item=>{
item.label = item.label + "经过webworker可以做更多的处理"
return item
})
postMessage(data);
}
// server.js
const Koa = require('koa');
var cors = require('koa2-cors');
var app = new Koa();
app.use(cors());
var option = []
var i = 100000
while(i){
option.push({
value: '选项'+i,
label: '北京烤鸭'+i
})
i--
}
app.use(async (ctx, next) => {
console.log(ctx.request.path)
if (ctx.request.path === '/getData') {
console.log(ctx.request)
var p = new Promise(function(resolve){
setTimeout(function () {
resolve(option)
},3000)
})
ctx.response.body = await p;
} else {
await next();
}
});
app.use(async(ctx, next) => {
await next();
ctx.response.type = 'text/html';
ctx.response.body = '<h1>Hello, koa2!</h1>';
});
app.listen(3000);