webWorker分页处理10w数据,实现下拉不卡顿

1,232 阅读2分钟
  • 最近看了知乎上的一个问题

    当后端一次性丢给你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返回给主线程

    • 主线程监听子线程返回的数据,使用数据

  • 分页加载效果图如下(如果直接加载的话,我的网页是直接崩溃的)

    UtUJA0.gif

  • 代码实现如下

// 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);