后端丢给你10万条数据如何渲染到表格里面不卡顿
本文直接讲实战,不讲虚的,有源码,有展示。后端如果丢给你10万条数据你如果去展示???
话不多说直接给结果。
页面有多大量我们首页就给他展示多大量,后面所以的操作就是可以监听,那么在监听过程中去展示操作行为后对应的数据不就可以了吗。
本着可视化展示原则。
就是在屏幕有限的可视范围内展示数据,再通过表格的监听事件,比如滚动条滚动,鼠标滚动,拖动等等操作行为最后对操作行为给出对应的展示。
预览地址
源码地址
|-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里面
转载自:https://juejin.cn/post/7052146867462733854