前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >简单聊一下virtual-list

简单聊一下virtual-list

作者头像
进击的小进进
发布2020-03-19 15:34:10
9800
发布2020-03-19 15:34:10
举报
文章被收录于专栏:前端干货和生活感悟

比如现在有一千条数据,生成 DOM 实例并渲染到页面上,是非常卡的:

代码语言:javascript
复制
export default function App() {
  let i=0
  const divArr=[]

  while(i<=1000){
    divArr.push(<div key={i} style={{border:'1px black solid',height:20,}}>{i}</div>)
    i++
  }

  return (
    <div style={{height:300,}}>
        {divArr}
    </div>
  );
}

virtual-list 的原理就是只渲染出可视区域的数据,而不可见的数据用空白元素填充,同时滚动条用假滚动,让用户认为是列表滚动以显示数据的:

换句话叫按需加载,同时缓存已加载的数据

基本实现代码

代码语言:javascript
复制
export default function App() {
  const all = 1000
  //每个 item 高度
  const divHeight = 20
  //显示 item 的数量
  const divNum = 15

  const [divArr, setDivArr] = React.useState([])
  const [startNum, setStartNum] = React.useState([])

  return (
    <div style={{
      marginTop:30,
      height: 300,
      width: 300,
      overflow: 'auto',
      position: 'relative',
      border: '1px solid red',
      textAlign: 'center',
      left: '50%',
    }}
         onScroll={(e) => {
           const newStartNum = Math.ceil(e.target.scrollTop / divHeight)
           const startHeight = newStartNum * divHeight

           const endNum = newStartNum + divNum
           const endHeight = startHeight + divNum * divHeight
           const newDivArr = []

           //start 和 end 都再多渲染一条,不然往下滚动出现白色间隙不好看
           for (let i = newStartNum ; i < endNum; i++) {
             //起始位置多加一条
             if (i === newStartNum) {
               newDivArr.push(<div style={{backgroundColor: '#4a7bf7', height: divHeight, paddingRight: 20,}}
                                   key={i-1}>{i-1}</div>)
             }
             newDivArr.push(<div style={{backgroundColor: '#4a7bf7', height: divHeight, paddingRight: 20,}}
                                 key={i}>{i}</div>)
             //结束位置多加一条
             if (i === endNum) {
               newDivArr.push(<div style={{backgroundColor: '#4a7bf7', height: divHeight, paddingRight: 20,}}
                                   key={i}>{i}</div>)
             }
           }

           setDivArr(newDivArr)
           setStartNum(newStartNum)


         }}
    >
      {/*空白元素的高度,目的是让滚动条 scroll*/}
      <div style={{
        height: (startNum - 1) * divHeight,
      }}>

      </div>
      {/*实际渲染的列*/}
      <div style={{

        height: (all - startNum + 1) * divHeight,
      }}>
        {divArr}
      </div>

    </div>
  );
}

缓存

可通过一个cacheObject来保存 data[i] 的 value、height(根据 value 的长度计算)、offsetY(根据 height 和 index 来计算)(index 为 key),在循环渲染固定数量的 item 时,先从cacheObject中根据 index 判断该 item 是否存在,存在即从cacheObject中获取,否则需要计算itemheightoffsetY

session 最大可存储5mb数据,大约是 250 万个字符,绰绰有余,使用频繁的话,localstorage更好

搜索

用户是不可能一直往下翻,来 select 的,所以搜索功能是必须的。

注意: 下面两种方法只适用于有序的 number 数组

二分法

代码语言:javascript
复制
 function middleSearch(arr, start, end, value) {
    //用户输入的 value
    // let value = -1

    // let start = 0
    // let end = arr.length - 1
    let middle
    while (start <= end) {
      //中间值
      middle = Math.floor((start + end) / 2)
      //如果输入的值正好是中间值的话
      if (value === arr[middle]) {
        //终止循环
        return middle
      } else if (arr[middle] > value) {
        end = middle - 1
      } else {
        start = middle + 1
      }
    }
    if (!middle) {
      middle = -1
    }
    return middle
  }

搜索指数法 简单说是游标的 index 不断乘 2,直到 arr[index] >= 查找的 value,此时就确定了 index 的范围,进而用二分法解决,看代码就懂了:

代码语言:javascript
复制
  //=============指数搜索====================
  // Returns position of first occurrence of
  // x in array
  //arr:[2, 3, 4, 10, 20,30,40]
  //length:5
  //value:10
  function exponentialSearch(arr, length, value) {
    //先判断 data 的第一个 value 是否是要查找的 value
    //因为指数搜索要从 1 开始( 0*2 一直是 0)
    if (arr[0] === value) {
      return 0;
    }

    //从 index=1 开始,查找范围
    let start = 1;
    let end = 1;
    //i 最终为 4
    while (end < length && arr[end] <= value) {
      start = start * 2;
      end = end * 2;
    }

    //确定查找范围是 index 2~4 后,在该范围内进行「二分」查找
    //注意:
    //(1) 由于是先 *2 再判断范围,所以当条件成立后,start 必定是多乘了 2 的
    //(2) 由于是先 *2 再判断范围,所以end 可能会超出 length
    return middleSearch(arr, start / 2, Math.min(end, length), value);

  }

  function main() {
    //data 数组(numnber 类型的递增数组)
    const arr = [2, 3, 4, 10, 40]
    //用户输入的值
    const value = 10;
    //查找的结果(返回下标)
    //指数搜索
    const result = exponentialSearch(arr, arr.length, value);

    console.log(result < 0 ? "Element is not present in array" : "Element is present at index " + result);
  }

  main()

仍然不理解的话,请看:https://www.geeksforgeeks.org/exponential-search/

最后

感兴趣,并且想深入理解的话,建议看下源码: https://github.com/react-component/virtual-list

现在项目业务上没有用到 virtual-list,所以我暂时不解析该源码了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-03-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 webchen 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本实现代码
  • 缓存
  • 搜索
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档