美文网首页
简单聊一下virtual-list

简单聊一下virtual-list

作者: 小进进不将就 | 来源:发表于2020-07-16 10:39 被阅读0次

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

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

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


    基本实现代码

    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 数组

    二分法

     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 的范围,进而用二分法解决,看代码就懂了:

      //=============指数搜索====================
      // 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,所以我暂时不解析该源码了。


    (完)

    相关文章

      网友评论

          本文标题:简单聊一下virtual-list

          本文链接:https://www.haomeiwen.com/subject/xjkgjhtx.html