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
原文地址:https://www.cnblogs.com/renzhiwei2017/p/15761303.html