后端丢给你10万条数据如何渲染到表格里面不卡顿

9,743 阅读2分钟

屏幕截图 2022-01-12 101714.png 本文直接讲实战,不讲虚的,有源码,有展示。后端如果丢给你10万条数据你如果去展示???

话不多说直接给结果。

页面有多大量我们首页就给他展示多大量,后面所以的操作就是可以监听,那么在监听过程中去展示操作行为后对应的数据不就可以了吗。

本着可视化展示原则。

就是在屏幕有限的可视范围内展示数据,再通过表格的监听事件,比如滚动条滚动,鼠标滚动,拖动等等操作行为最后对操作行为给出对应的展示。

预览地址

tcly861204.github.io/vite-react-…

源码地址

github.com/tcly861204/…

|-src
  |--main.jsx
  |--App.jsx
  |--libs
      |--utils.jsx
  |--bigdata
      |--body.jsx
      |--checkbox.jsx
      |--checkstyle.scss
      |--header.jsx
      |--index.jsx
      |--style.scss

项目只是写出了解决方案,最终居多细节需要自己去处理

源码解读

/*
  @Author: tcly861204
  @Email:  356671808@qq.com
  @Date:   2022/1/7 下午12:00:45
  @Last Modified by:   tcly861204
  @Last Modified time: 2022/1/7 下午12:00:45
  @Github: https://tcly861204.github.io
*/
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'
import Header from './header'
import Body from './body'
import PropTypes from 'prop-types'
import './style.scss'
const BigData = (props) => {
  const { data, handleSelected } = props
  const bodyNode = useRef(null)
  const [columns, setColumns] = useState(props.columns || [])
  const [scrollWidth, setScrollWidth] = useState(8)
  const [scrollTop, setScrollTop] = useState(0)
  const [scrollLeft, setScrollLeft] = useState(0)
  const [leftColumns, setLeftColumns] = useState([])
  const [rightColumns, setRightColumns] = useState([])
  const [contentColumns, setContentColumns] = useState([])
  const [rowHeight] = useState(props.rowHeight || 36)
  const [maxRow] = useState(props.maxRow || 15)
  const [isScroll, setIsScroll] = useState(false)
  useEffect(() => {
    setLeftColumns(columns.filter((item) => item.fixed === 'left'))
    setRightColumns(columns.filter((item) => item.fixed === 'right'))
    setContentColumns(
      columns.filter((item) => !['left', 'right'].includes(item.fixed))
    )
  }, [columns])
  useEffect(() => {
    const timer = setTimeout(() => {
      const width = bodyNode.current.clientWidth
      const pwidth = bodyNode.current.parentNode.clientWidth
      setScrollWidth(pwidth - width)
      return () => {
        clearTimeout(timer)
      }
    }, 30)
  }, [data, bodyNode])
  useEffect(() => {
    setIsScroll(data.length > maxRow)
  }, [data, maxRow])
  const handleResizeColumn = useCallback(
    function (field, width) {
      if (!field) {
        return
      }
      const index = columns.findIndex((item) => item.prop === field)
      if (index >= 0) {
        const newColumns = [...columns]
        newColumns[index].width = width < 20 ? 20 : width
        setColumns(newColumns)
      }
    },
    [columns]
  )
  const containerName = 'cui-bigdata-container'
  const leftWidth = useMemo(
    () =>
      leftColumns.reduce((acc, item) => {
        return acc + (item.width || 100)
      }, 0),
    [leftColumns]
  )
  const rightWidth = useMemo(
    () =>
      rightColumns.reduce((acc, item) => {
        return acc + (item.width || 100)
      }, 0),
    [rightColumns]
  )
  const controllerHeight =
    ((data.length > maxRow ? maxRow : data.length) + 1) * rowHeight
  const handleScroll = function (e) {
    const top = e.currentTarget.scrollTop
    const left = e.currentTarget.scrollLeft
    if (top !== scrollTop) {
      // 滚动的距离除以行高 = 滚动表格的下一行
      setScrollTop(Math.floor(top / rowHeight))
    }
    if (left !== scrollLeft) {
      setScrollLeft(left)
    }
  }
  const scrollHeight = data.length * rowHeight
  return (
    <section
      className={containerName}
      style={{ height: `${controllerHeight + 2}px` }}
    >
      <section className={`${containerName}-header`}>
        <div
          className={`${containerName}_left`}
          style={{ width: `${leftWidth}px` }}
        >
          <Header
            columns={leftColumns}
            callback={handleResizeColumn}
            handleSelected={handleSelected}
          />
        </div>
        <div
          className={`${containerName}_content`}
          style={{
            left: `${leftWidth - scrollLeft}px`,
            right: `${
              rightWidth + (isScroll ? scrollWidth : 0) - scrollLeft
            }px`,
          }}
        >
          <Header
            handleSelected={handleSelected}
            columns={contentColumns}
            callback={handleResizeColumn}
          />
        </div>
        <div
          className={`${containerName}_right`}
          style={{
            width: `${rightWidth + (isScroll ? scrollWidth : 0)}px`,
            right: 0,
          }}
        >
          <Header columns={rightColumns} callback={handleResizeColumn} />
        </div>
      </section>
      <section
        className={`${containerName}-body`}
        style={{ height: `${controllerHeight - 36}px` }}
        onScroll={handleScroll}
        ref={bodyNode}
      >
        <div
          className={`${containerName}_left`}
          style={{
            width: `${leftWidth}px`,
            left: `${scrollLeft}px`,
            height: `${scrollHeight}px`,
          }}
        >
          <Body
            {...props}
            maxRow={maxRow}
            scrollTop={scrollTop}
            columns={leftColumns}
          />
        </div>
        <div
          className={`${containerName}_content`}
          style={{
            left: `${leftWidth}px`,
            right: `${rightWidth - scrollLeft}px`,
            height: `${scrollHeight}px`,
          }}
        >
          <Body
            {...props}
            maxRow={maxRow}
            scrollTop={scrollTop}
            columns={contentColumns}
          />
        </div>
        <div
          className={`${containerName}_right`}
          style={{
            width: `${rightWidth}px`,
            right: `${0 - scrollLeft}px`,
            height: `${scrollHeight}px`,
          }}
        >
          <Body
            {...props}
            maxRow={maxRow}
            scrollTop={scrollTop}
            columns={rightColumns}
          />
        </div>
        <div
          className={`${containerName}_scroll`}
          style={{ height: `${scrollHeight}px` }}
        />
      </section>
    </section>
  )
}

BigData.propTypes = {
  data: PropTypes.array,
  handleSelected: PropTypes.func,
  columns: PropTypes.array,
  rowHeight: PropTypes.number,
  maxRow: PropTypes.number,
}

export default BigData

上面大部分处理就是去展示页面,布局等等,核心的东西就是监听body区域滚动条滚动的距离 如何获取滚动的位置,然后top/rowHeight就能获取到滚动条滚动了多少刚好是滚动了一行表格的距离,就拿这个就可以去计算表格需要展示的数据了,我这里默认表格展示15条数据

/*
  @Author: tcly861204
  @Email:  356671808@qq.com
  @Date:   2022/1/7 下午12:00:45
  @Last Modified by:   tcly861204
  @Last Modified time: 2022/1/7 下午12:00:45
  @Github: https://tcly861204.github.io
*/
import React from 'react'
import PropTypes from 'prop-types'
import Checkbox from './checkbox'
function Body(props) {
  const { data, scrollTop, maxRow, columns, deleteCallback } = props
  const len = data.length > 15 ? 15 : data.length
  const renderCell = function (item, index) {
    if (item.component && item.type !== 'delete') {
      return <item.component index={index} />
    } else if (item.prop) {
      return <div className="cell line-clamp">{data[index][item.prop]}</div>
    } else {
      switch (item.type) {
        case 'index':
          return <div className="cell">{index + 1}</div>
        case 'selection':
          return (
            <div className="cell">
              <Checkbox value={data[index]._checked || false} />
            </div>
          )
        case 'delete':
          if (item.component) {
            return <item.component index={index} callback={deleteCallback} />
          }
          return null
        default:
          return null
      }
    }
  }
  const renderColumns = function () {
    const rows = []
    let maxLen = 0
    const className = 'cui-bigdata-container'
    const dataLen = data.length
    if (dataLen > maxRow) {
      if (scrollTop < dataLen - maxRow - 4) {
        maxLen = scrollTop + maxRow + 3
      } else {
        maxLen = dataLen
      }
    } else {
      maxLen = data.length
    }
    for (let row = scrollTop; row < maxLen; row++) {
      rows.push(
        <div
          key={row}
          className={`${className}-row`}
          style={{ transform: `translateY(${row * 36}px)` }}
        >
          {columns.map((item, col) => {
            return (
              <li
                key={col}
                className={`align-${item.align || 'left'}`}
                style={
                  item.width
                    ? {
                        width: `${item.width}px`,
                        maxWidth: `${item.width}px`,
                        minWidth: `${item.width}px`,
                      }
                    : {
                        display: 1,
                      }
                }
              >
                {renderCell(item, row)}
              </li>
            )
          })}
        </div>
      )
    }
    return rows
  }
  return (
    <div className="table-body" style={{ height: `${len * 36}px` }}>
      {renderColumns()}
    </div>
  )
}

Body.propTypes = {
  data: PropTypes.array.isRequired,
  scrollTop: PropTypes.number.isRequired,
  maxRow: PropTypes.number.isRequired,
  columns: PropTypes.array,
  deleteCallback: PropTypes.func,
}

export default Body

上面拿到了每次滚动的时候的scrollTop其实就是对应数据在可售区域的开始索引startIndex 然后加上默认展示的15就是最终展示的渲染数据

总结

所有的大数据都是通过尽可能的减少dom的渲染来达到优化性能的,最后发现react在做这种大的数据渲染好像比vue性能更好,我是先用vue写的后面同理实现了一版react的, 里面还有更多性能优化我留在了vue里面