• react进阶第九讲:渲染海量数据


    方式1:时间片段

    将一次性任务划分成多个片段(Fragments),每次只完成一部分。

    将10w个彩色点渲染到dom:

    生成色块代码:

    /* 获取随机颜色 */
    function getColor(){
        const r = Math.floor(Math.random()*255);
        const g = Math.floor(Math.random()*255);
        const b = Math.floor(Math.random()*255);
        return 'rgba('+ r +','+ g +','+ b +',0.8)';
     }
    /* 获取随机位置 */
    function getPostion(position){
         const { width , height } = position
         return { left: Math.ceil( Math.random() * width ) + 'px',top: Math.ceil(  Math.random() * height ) + 'px'}
    }
    /* 色块组件 */
    function Circle({ position }){
        const style = React.useMemo(()=>{ //用useMemo缓存,计算出来的随机位置和色值。
             return {  
                background : getColor(),
                ...getPostion(position)
             }
        },[])
        return <div style={style} className="circle" />
    }
    
    
    CSS:
    
    .bigData_index{
      position: fixed;
      left:0;
      right: 0;
      top:0;
      bottom: 0;
      
    }
    
    .circle{
      position: absolute;
       7px;
      height: 7px;
      border-radius: 50%;
    }
    
    .list{
      list-style: none;
      background-color: #fc4838;
      padding: 10px 20px;
      color: #fff;
      height: 50px;
      line-height: 50px;
      box-sizing: border-box;
      margin-bottom: 10px;
      margin-left: 24px;
      margin-right: 24px;
      font-weight: bold;
      border-radius:10px ;
    }
    
    
    .list_box{
      position: fixed;
      left:0;
      top:60px;
      overflow: scroll;
      bottom:0;
      right: 0;
    }
    

    未优化的代码:

    class Index extends React.Component{
        state={
            dataList:[],                  // 数据源列表
            renderList:[],                // 渲染列表
            position:{ 0,height:0 } // 位置信息
        }
        box = React.createRef()
        componentDidMount(){
            const { offsetHeight , offsetWidth } = this.box.current
            const originList = new Array(20000).fill(1)
            this.setState({
                position: { height:offsetHeight,offsetWidth },
                dataList:originList,
                renderList:originList,
            })
        }
        render(){
            const { renderList, position } = this.state
            return <div className="bigData_index" ref={this.box}  >
                {
                    renderList.map((item,index)=><Circle  position={ position } key={index}  /> )
                }
            </div>
        }
    }
    /* 控制展示Index */
    export default ()=>{
        const [show, setShow] = useState(false)
        const [ btnShow, setBtnShow ] = useState(true)
        const handleClick=()=>{
            setBtnShow(false)
            setTimeout(()=>{ setShow(true) },[])
        } 
        return <div>
            { btnShow &&  <button onClick={handleClick} >show</button> } 
            { show && <Index />  }
        </div>
    }
    

    10WDOM一起渲染,界面会出现明显的卡顿。优化后代码:

    class Index extends React.Component{
      state={
          dataList:[],                  // 数据源列表
          renderList:[],                // 渲染列表
          position:{ 0,height:0 }, // 位置信息
          eachRenderNum:500,  // 每次渲染数量
        }
      box = React.createRef()
      componentDidMount(){
          const { offsetHeight , offsetWidth } = this.box.current
          const originList = new Array(100000).fill(1)
          const times = Math.ceil(originList.length / this.state.eachRenderNum) /* 需要渲染此次数*/
          let index = 1
    
          this.setState({
              position: { height:offsetHeight,offsetWidth },
              dataList:originList
          },()=>{
            this.toRenderList(index,times)
          })
      }
      toRenderList=(index,times)=> {
        if(index===times) return
        const { renderList } = this.state
        renderList.push(this.renderNewList(index))
        this.setState({
            renderList,
        })
        setTimeout(()=>{ /* 用 requestIdleCallback 代替 setTimeout */
            this.toRenderList(++index,times)
        },0)
      }
      renderNewList(index) {
        const { dataList , position , eachRenderNum } = this.state
        const list = dataList.slice((index-1) * eachRenderNum , index * eachRenderNum  )
        return <React.Fragment key={index} >
            {  
                list.map((item,index)=>{
                    return <Circle key={index} position={position}  />
                })
            }
        </React.Fragment>
      }
    
      render(){
          return <div className="bigData_index" ref={this.box}  >
              { this.state.renderList }
          </div>
      }
    }
    
    /* 控制展示Index */
    export default ()=>{
      const [show, setShow] = useState(false)
      const [ btnShow, setBtnShow ] = useState(true)
      const handleClick=()=>{
          setBtnShow(false)
          setTimeout(()=>{ setShow(true) },[])
      } 
      return <div>
          { btnShow &&  <button onClick={handleClick} >show</button> } 
          { show && <Index />  }
      </div>
    }
    

    重点:

    1. 将数据分割,形成二维数组[[1,1,...,1], [1,1,...,1], ..., [1,1,...,1]]
    2. 使用React.Fragment 分割成代码片段去渲染

    方式二:虚拟列表

    虚拟列表划分可以分为三个区域:视图区 + 缓冲区 + 虚拟区。
    image

    • 通过 useRef 获取元素,缓存变量。
    • useEffect 初始化计算容器的高度。截取初始化列表长度。这里需要 div 占位,撑起滚动条。
    • 通过监听滚动容器的 onScroll 事件,根据 scrollTop 来计算渲染区域向上偏移量。
    • 通过重新计算 end 和 start 来重新渲染列表。
    function VirtualList(){
       const [ dataList,setDataList ] = React.useState([])  /* 保存数据源 */
       const [ position , setPosition ] = React.useState([0,0]) /* 截取缓冲区 + 视图区索引 */
       const scroll = React.useRef(null)
       const box = React.useRef(null)
       const context = React.useRef(null)
       const scrollInfo = React.useRef({ 
           height:500,     /* 容器高度 */
           bufferCount:8,  /* 缓冲区个数 */
           itemHeight:60,  /* 每一个item高度 */
           renderCount:0,  /* 渲染区个数 */ 
        }) 
        React.useEffect(()=>{
            const height = box.current.offsetHeight
            const { itemHeight , bufferCount } = scrollInfo.current
            const renderCount =  Math.ceil(height / itemHeight) + bufferCount
            scrollInfo.current = { renderCount,height,bufferCount,itemHeight }
            const dataList = new Array(10000).fill(1).map((item,index)=> index + 1 )
            setDataList(dataList)
            setPosition([0,renderCount])
        },[])
       const handleScroll = () => {
           const { scrollTop } = box.current
           const { itemHeight , renderCount } = scrollInfo.current
           const currentOffset = scrollTop - (scrollTop % itemHeight) 
           const start = Math.floor(scrollTop / itemHeight)
           context.current.style.transform = `translate3d(0, ${currentOffset}px, 0)`
           const end = Math.floor(scrollTop / itemHeight + renderCount + 1)
           if(end !== position[1] || start !== position[0]  ){ /* 如果render内容发生改变,那么截取  */
                setPosition([ start , end ])
           }
       } 
       const { itemHeight , height } = scrollInfo.current
       const [ start ,end ] = position
       const renderList = dataList.slice(start,end)
       return <div className="list_box" ref={box}  onScroll={ handleScroll } >
         <div className="scroll_box" style={{ height: height + 'px'  }} ref={scroll}  >
         <div className="context" ref={context}> 
                {
                   renderList.map((item,index)=> <div className="list" key={index} >  {item + '' } Item </div>)
                }  
            </div>
            <div className="scroll_hold" style={{ height: `${dataList.length * itemHeight}px` }}  />
         </div>
       </div>
    }
    
    export default VirtualList
    
  • 相关阅读:
    Area--->AreaRegistrationContext.MapRoute
    Area--->AreaRegister.RegisterAllArea()与Area区域的解析
    Area的使用
    ViewData、ViewBag、TempData、Session的区别与联系
    设计模式---装饰模式(转自可均可可博客)
    设计模式---桥接模式(转自可均可可博客)
    设计模式---适配器模式(转自可均可可博客)
    设计模式---原型模式(转自可均可可博客)
    设计模式---简单工厂、工厂方法、抽象工厂
    ASP.NET MVC Controller激活系统详解1
  • 原文地址:https://www.cnblogs.com/renzhiwei2017/p/15761303.html
Copyright © 2020-2023  润新知