美文网首页
useVirtualList

useVirtualList

作者: skoll | 来源:发表于2022-03-09 10:20 被阅读0次

    简介

    1 .虚拟化列表能力的hook,用于展示海量数据渲染时首屏渲染缓慢和卡顿的问题

    扩展

    1 .和无限列表相结合,每次滚动到最下方,就拉取一些新的元素

    import React,{useEffect,useState,useRef,useMomo} from 'react'
    import {useVirtualList,useInViewport} from 'ahooks'
    
    export default function Drag(props){
        const containerRef=useRef()
        const wrapperRef=useRef()
        const footerRef=useRef()
    
        const [origin,setOrigin]=useState([1,2,3,4,5,6,7,8,9,10])
        const [inViewport]=useInViewport(footerRef)
    
        const [list]=useVirtualList(origin,{
            containerTarget:containerRef,
            wrapperTarget:wrapperRef,
            itemHeight:60,
            overscan:1,
        })
        
        function handleAdd(){
            let data=[]
            for(let i=origin.length+1;i<origin.length+5;i++){
                data.push(i)
            }
    
            // 假设在请求资源
            setTimeout(()=>{
                setOrigin([...origin,...data])
                console.log(origin,'当前')
            },2000)
            
        }
    
        useEffect(()=>{
            console.log(inViewport,'outer加一组数据')
            if(inViewport){
                console.log(inViewport,'inner加一组数据')
                handleAdd()
            }
        },[inViewport])
        return (
            <div>
                <div ref={containerRef} style={{height:'200px',overflow:'auto',border:'1px solid #eee'}}>
                    <div ref={wrapperRef}>
                        {list.map((el)=>{
                            if(el.index===origin.length-1){
                                return <div  ref={footerRef} style={{color: inViewport ? '#87d068' : '#f50' ,
                                    height: 52,
                                    display: 'flex',
                                    justifyContent: 'center',
                                    alignItems: 'center',
                                    border: '1px solid #e8e8e8',
                                    marginTop: 8,
                                }}
                                onClick={handleAdd}
                                // 万一没有触发,就加一个手动的点击
                                >
                                {inViewport?"正在加载":"更多"}
                            </div>
                            }else{
                                return <div
                                key={el.index}
                                style={{
                                    height: 52,
                                    display: 'flex',
                                    justifyContent: 'center',
                                    alignItems: 'center',
                                    border: '1px solid #e8e8e8',
                                    marginTop: 8,
                                }}
                                >{JSON.stringify(el)}</div>
                            }
                        })}
                        
                    </div>
                    
                </div>
                <button onClick={handleAdd}>{inViewport?'visible':'hidden'}</button>
    
            </div>
        )
    }
    
    
    

    2 .问题1,const [inViewport]=useInViewport(footerRef) 发现元素出现的时候会弹好几次参数


    image.png
    1 .margin的时候就算出现了,不精确
    

    3 .问题2,const [inViewport]=useInViewport(footerRef)首次没渲染的时候,有这个标记,就会里面拉取一下.导致一开始就有问题

    这里这个可以当作loading界面,初次加载的时候这里数据默认为空,一到这个页面,发现在里面就去加载数据吧,加载了新数据把他顶到下面,看不见
    1 .首次加载的时候他的值变化是undefind,true,false
    

    3 .问题2 ,之前最麻烦的时候东西就是动态高度.这就要求.请求到数据,算出来他的高度,然后把高度加到原来的里面,这时候算完,再把他当成一个知道高度的放到原来的里面.看别的库的api或者思路来说,是会有一个固定高度的,先用这个,然后大小发生改变的时候,算出改变之后的高度,数据也是动态增加的,不是一直就有的

    MutationObserver API
    import React,{useEffect,useState,useRef} from 'react'
    import {useVirtualList,useInViewport} from 'ahooks'
    
    export default function Drag(props){
        const [obj,setObj]=useState({})
        const id='1'
        useEffect(()=>{
            setTimeout(()=>{
                setObj(Object.assign({},{name:'刘华强',age:20}))
            },1000)
    
    
            let observer=new MutationObserver(()=>{
                const {height} =document.getElementById(id).getBoundingClientRect()
                console.log('发生了变化',height)
                // 然后把这个值给广播出去
            })
    
            observer.observe(document.getElementById(id),{
                childList:false,
                // 子节点的新增,删除,更改能否被检测
                attributes:false,
                //属性的变动能否被检测
                characterData:true,
                // 节点内容或节点文本的边动能否被检测:我们这里是只会有文字新增,所以这里选这个,别的检测都关掉
                subtree:true,
                // 是否应用于该节点的所有后代节点:为啥必须是这个
            })
    
            return ()=>{
                observer.disconnect()
                // 组件消除的时候取消观察
            }
        },[])
    
        return (
            <div>
                <div id={id} style={{lineHeight:20}}>
                    {JSON.stringify(obj)}
                </div>
            </div>
        )
    }
    
    
    
    //方法2:在数据变化的时候,求大小,但是这不是加在回调里面,感觉可能不会很准,虽然现在能看到值,不是很保险,但是简单
     const [obj,setObj]=useState({})
        const id='1'
        useEffect(()=>{
            setTimeout(()=>{
                setObj(Object.assign({},{name:'刘华强',age:20}))
            },1000)
            const node=document.getElementById(id)
            const height=node.getBoundingClientRect().height
            console.log(height)
        },[])
        return (
            <div>
                <div id={id} style={{lineHeight:20}}>
                    {JSON.stringify(obj)}
                </div>
            </div>
        )
    

    问题3 .添加数据的时候,进度条高度会有闪动,不是很稳定,用react-window来实现一下这个吧

    import React,{useEffect,useState,useRef} from 'react'
    import { VariableSizeList as List } from 'react-window'
    
    export default function Drag(props){
        // 高度不确定
        // 动态增加数据
        const data=[]
        //全部的数据,会动态增加
    
        const rowSize=[]
        // 全部item的高度
    
        function getItemSize(index){
            console.log('idx',index,rowSize[index-1])
            if(rowSize[index]){
                return rowSize
            }else{
                return 21.97
            }
        }
    
        function Row({index,style}){
            const rowRef=useRef()
            useEffect(()=>{
                let observer=new MutationObserver(()=>{
                    const {height} =rowRef.current.getBoundingClientRect()
                    rowSize[index]=height
                    console.log('触发了变化',rowSize)
                })
                
                rowSize[index]=rowRef.current.getBoundingClientRect().height
                console.log(rowRef.current,'初始的高度',rowSize)
                observer.observe(rowRef.current,{
                    childList:false,
                    // 子节点的新增,删除,更改能否被检测
                    attributes:false,
                    //属性的变动能否被检测
                    characterData:true,
                    // 节点内容或节点文本的边动能否被检测:我们这里是只会有文字新增,所以这里选这个,别的检测都关掉
                    subtree:true,
                    // 是否应用于该节点的所有后代节点:为啥必须是这个
                })
    
                return ()=>{
                    observer.disconnect()
                    // 组件消除的时候取消观察
                }
            },[])
    
            // setTimeout(()=>{
            //     setValue(index+Math.random()*1000)
            // },1000) 
    
            return (
                <div style={style} className={index % 2 ? 'ListItemOdd' : 'ListItemEven'} ref={rowRef}>
                    {/* 子项里面的style,必须要绑定,可以看成是需要从父元素继承他需要算的一些东西 */}
                    Row {index}<br/>
                    {index%2?<span>加一行</span>:""}
                </div>
            )
        }
    
        const Example=()=>(
            <List
                height={200}
                itemCount={20}
                width={500}
                itemSize={getItemSize}
            >
                {Row}
            </List>
        )
        return (
            <div>
                <Example />
            </div>
        )
    }
    
    
    

    问题4 .useRef无效的问题:结果是绑定错了

    import React, { useRef, useEffect } from "react";
    import ReactDOM from "react-dom";
    
    import "./styles.css";
    
    function App() {
      const observed = useRef(null);
    
      useEffect(() => {
        console.log(observed.current);
      }, [observed]);
    
      return (
        <div ref={observed} className="App">
          <h1>Hello CodeSandbox</h1>
          <h2>Start editing to see some magic happen!</h2>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    
    

    react-window不支持动态高度的

    const rowHeights = new Array(1000)
      .fill(true)
      .map(() => 25 + Math.round(Math.random() * 50));
     
    const getItemSize = index => rowHeights[index];
    
    

    1 .最关键的是这种每一个元素的动态高度是拿不到的.如何提前知道呢,难道是没显示的时候就先渲染出来么..感觉也是不可能的
    2 .还有就是渲染出来告诉父组件这个现在的高度,然后重新刷新.但是问题来了

    <div style={style} className={index % 2 ? 'ListItemOdd' : 'ListItemEven'} ref={rowRef}>
                    {/* 子项里面的style,必须要绑定,可以看成是需要从父元素继承他需要算的一些东西 */}
                    Row {index}<br/>
                    {index%2?<span>加一行</span>:""}
                </div>
    //子组件需要接收style,这个style就是父组件传下来的每一个子元素的css样式,已经定死了height,搞得你没法算最新的高度,而且就算算出来也不一定能更新出来
    

    useVirtualList实现动态高度

    import React, { useMemo, useRef,useEffect,useCallback,useState} from 'react';
    import { useVirtualList } from 'ahooks';
    
    export default function Drag (){
      const containerRef = useRef();
      const wrapperRef = useRef();
      const originalList = useMemo(() => Array.from(Array(20).keys()), []);
      
      const itemHeightArray=useRef([])
      //存储了所有的高度  
      const lastComputed=useRef(0)
    
    
      const [list,] = useVirtualList(originalList, {
        containerTarget: containerRef,
        wrapperTarget: wrapperRef,
        itemHeight: (i) =>{
            // if(itemHeightArray.current[i]){
            //     return itemHeightArray.current[i]
            // }else{
            //     return 21.99
            // }
            return 42
        },
        overscan: 10,
      });
    
      function Item(props){
            const itemRef=useRef()
            useEffect(()=>{
                if(props.ele.index<lastComputed.curent){
                  return ()=>{
                    console.log('已经做过计算了')
                  }
                }else if(props.index>lastComputed.current){
                  lastComputed.current=props.ele.index
                }
    
    
                let observer=new MutationObserver(()=>{
                    console.log('发生了变化')
                })
    
                // 初次加载的时候更新一波高度
                itemHeightArray.current[props.ele.index]=itemRef.current.getBoundingClientRect().height
    
                observer.observe(itemRef.current,{
                    childList:false,            
                    attributes:false,             
                    characterData:true,
                    subtree:true,              
                })
    
                return ()=>{
                    observer.disconnect()
                }
            },[])
    
          return (
            <div    
                  ref={itemRef}
                  style={{
                    height: props.ele.index % 2 === 0 ? 42 :42,
                    // 高度做成自定义的
                    display: 'flex',
                    justifyContent: 'center',
                    alignItems: 'center',
                    border: '1px solid #e8e8e8',
                    marginBottom: 8,
                    lineHeight:1.5,
                  }}
                  key={props.ele.index}
                >
                  Row: {props.ele.data}<br/>
                  {props.ele.index % 2 === 0 ? 'small' : ''}
                </div>
          )
      }
      return (
        <div>
          <div ref={containerRef} style={{ height: '300px',
           overflow: 'auto',
           width:400 }}>
            <div ref={wrapperRef}>
              {list.map((ele) => (
                  <Item ele={ele}/>
              ))}
            </div>
          </div>
        </div>
      );
    };
    

    性能分析

    image.png

    1 .脚本执行花费了那么多时间吗?去掉计算动态高度的代码,也是这么多脚本执行时间,问题出在哪里呢


    image.png

    2 .不新开函数立马就下来了,难道还和这个有关系?离谱啊

     function It(){
        return (
          <div>
            测试1
          </div>
        )
      }
    //竟然真的是这样,这么简单的一个组件,里面没有任何逻辑,按照理解应该是和上面的一样啊,竟然脚本计算时间瞬间就上去了.但是在官方的demo里面这样改就没问题,脚本计算没这个爆炸.为啥呢
    

    3 .用新函数开一个item性能这么差么
    4 .普通渲染的差距看下,在普通的列表里面,是没关系的.那就是这个组件里面的差距,这感觉要翻下源码.
    5 .组件的时候scroll事件占了大部分的时间
    6 .不用组件的时候,出现的事件确是whell事件.还有指针事件,算了,看下他的源码吧,为什么框架里面只绑定了scroll事件,但是会触发各种各样的事件呢.pointerout,mouseout,scroll

    最后

    1 .目前是想要的效果实现了,帧数有时候会跑不满60,需要优化
    2 .还有就是会出现布局抖动的问题,这个是看浏览器出现的,后续也要研究下

    相关文章

      网友评论

          本文标题:useVirtualList

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