惠青 發表於 2020-12-9 20:30:00

react antDesign hook 大数据表格虚拟滚动

<h4 id="业务使用">业务使用</h4>
<pre><code>// 使用虚拟列表
const virtualComponents = useVirtualTable({
    height: 350 // 设置可视高度
})

const components = useMemo(() =&gt; {
    return {
      body: {
      wrapper: virtualComponents.body.wrapper
      },
      table: virtualComponents.table
    }
}, [ virtualComponents])


return (
    &lt;div className='commonTable' id='tablePart'&gt;
      &lt;Table
            {...otherProps}
            components={components}
      /&gt;
    &lt;/div&gt;
)
</code></pre>
<h4 id="hook">hook</h4>
<pre><code>/* eslint-disable no-case-declarations */
import React, { useRef, useEffect, useContext, createContext, useReducer, useState, useMemo } from 'react'
import { throttle, isNumber } from 'lodash'

interface initialStateProps {
rowHeight: number
curScrollTop: number
scrollHeight: number
tableScrollY: number
}
const initialState: initialStateProps = {
// 行高度
rowHeight: 40,
// 当前的scrollTop
curScrollTop: 0,
// 可滚动区域的高度
scrollHeight: 0,
// scrollY值
tableScrollY: 0
}
function reducer(state: initialStateProps, action: { type: string; : any }) {
switch (action.type) {
    // 改变trs 即 改变渲染的列表trs
    case 'changeTrs':
      // 获取值
      let curScrollTop = action.curScrollTop
      let scrollHeight = action.scrollHeight
      let tableScrollY = action.tableScrollY

      if (scrollHeight &lt;= 0) {
      scrollHeight = 0
      }

      if (state.scrollHeight !== 0) {
      if (tableScrollY === state.tableScrollY) {
          scrollHeight = state.scrollHeight
      }
      }

      if (state.scrollHeight &amp;&amp; curScrollTop &gt; state.scrollHeight) {
      curScrollTop = state.scrollHeight
      }

      return {
      ...state,
      curScrollTop,
      scrollHeight,
      tableScrollY
      }

    case 'reset':
      return {
      ...state,
      curScrollTop: 0,
      scrollHeight: 0
      }
    default:
      throw new Error()
}
}

interface ScrollContextProps {
dispatch: undefined | ((arg: { type: string; : any }) =&gt; void)
renderLen: number
start: number
offsetStart: number
rowHeight: number
totalLen: number
}
const ScrollContext = createContext&lt;ScrollContextProps&gt;({
dispatch: undefined,
renderLen: 1,
start: 0,
offsetStart: 0,
rowHeight: 40,
totalLen: 0
})

let scrollY: number | string = 0

function VWrapper(props: any): JSX.Element {
const { children, ...restProps } = props

const { renderLen, start, offsetStart } = useContext(ScrollContext)

let contents = children

let tempNode = null
if (Array.isArray(contents) &amp;&amp; contents.length) {
    tempNode = [
      children,
      contents.slice(start, start + renderLen).map(item =&gt; {
      if (Array.isArray(item)) {
          // 兼容antd v4.3.5 --- rc-table 7.8.1及以下
          return item
      } else {
          // 处理antd ^v4.4.0--- rc-table ^7.8.2
          return item
      }
      })
    ]
} else {
    tempNode = children
}

return (
    &lt;tbody {...restProps} style={{ transform: `translateY(-${offsetStart}px)` }}&gt;
      {tempNode}
    &lt;/tbody&gt;
)
}

function VTable(props: any): JSX.Element {
const { style, children, ...rest } = props
const { width, ...restStyle } = style

const = useReducer(reducer, initialState)

const wrapTableRef = useRef&lt;HTMLDivElement&gt;(null)
const tableRef = useRef&lt;HTMLTableElement&gt;(null)

// 数据的总条数
const = useState&lt;number&gt;(children?.props?.data?.length ?? 0)

useEffect(() =&gt; {
    if (isNumber(children?.props?.data?.length)) {
      setTotalLen(children?.props?.data?.length)
    }
}, )

// table总高度
const tableHeight = useMemo&lt;string | number&gt;(() =&gt; {
    let temp: string | number = 'auto'
    if (state.rowHeight &amp;&amp; totalLen) {
      temp = state.rowHeight * totalLen + 10
    }
    return temp
}, )

// table的scrollY值
let tableScrollY = 0
if (typeof scrollY === 'string') {
    tableScrollY = (wrapTableRef.current?.parentNode as HTMLElement)?.offsetHeight
} else {
    tableScrollY = scrollY
}

if (isNumber(tableHeight) &amp;&amp; tableHeight &lt; tableScrollY) {
    tableScrollY = tableHeight
}

// 处理tableScrollY &lt;= 0的情况
if (tableScrollY &lt;= 0) {
    tableScrollY = 0
}

// 渲染的条数
const renderLen = useMemo&lt;number&gt;(() =&gt; {
    let temp = 1
    if (state.rowHeight &amp;&amp; totalLen &amp;&amp; tableScrollY) {
      if (tableScrollY &lt;= 0) {
      temp = 0
      } else {
      let tempRenderLen = ((tableScrollY / state.rowHeight) | 0) + 1 + 2
      // console.log('tempRenderLen', tempRenderLen)
      temp = tempRenderLen &gt; totalLen ? totalLen : tempRenderLen
      }
    }
    return temp
}, )

// 渲染中的第一条
let start = state.rowHeight ? (state.curScrollTop / state.rowHeight) | 0 : 0
// 偏移量
let offsetStart = state.rowHeight ? state.curScrollTop % state.rowHeight : 0

// 用来优化向上滚动出现的空白
if (state.curScrollTop &amp;&amp; state.rowHeight &amp;&amp; state.curScrollTop &gt; state.rowHeight) {
    if (start &gt; totalLen - renderLen) {
      // 可能以后会做点操作
      offsetStart = 0
    } else if (start &gt; 1) {
      start = start - 1
      offsetStart += state.rowHeight
    }
} else {
    start = 0
}

useEffect(() =&gt; {
    // totalLen变化, 那么搜索条件一定变化, 数据也一定变化.
    let parentNode = wrapTableRef.current?.parentNode as HTMLElement
    if (parentNode) {
      parentNode.scrollTop = 0
    }
    dispatch({ type: 'reset' })
}, )

useEffect(() =&gt; {
    const throttleScroll = throttle(e =&gt; {
      const scrollTop: number = e?.target?.scrollTop ?? 0

      if (scrollTop !== state.curScrollTop) {
      const scrollHeight = e.target.scrollHeight - tableScrollY
      dispatch({
          type: 'changeTrs',
          curScrollTop: scrollTop,
          scrollHeight,
          tableScrollY
      })
      }
    }, 60)

    const ref = wrapTableRef?.current?.parentNode as HTMLElement

    if (ref) {
      ref.addEventListener('scroll', throttleScroll)
    }

    return () =&gt; {
      ref.removeEventListener('scroll', throttleScroll)
    }
}, )

return (
    &lt;div
      className='virtuallist'
      ref={wrapTableRef}
      style={{
      width: '100%',
      position: 'relative',
      height: tableHeight,
      boxSizing: 'border-box',
      paddingTop: state.curScrollTop
      }}
    &gt;
      &lt;ScrollContext.Provider
      value={{
          dispatch,
          rowHeight: state.rowHeight,
          start,
          offsetStart,
          renderLen,
          totalLen
      }}
      &gt;
      &lt;table
          {...rest}
          ref={tableRef}
          style={{
            ...restStyle,
            width,
            position: 'relative'
          }}
      &gt;
          {children}
      &lt;/table&gt;
      &lt;/ScrollContext.Provider&gt;
    &lt;/div&gt;
)
}

export default function useVirtualTable(props: { height: number | string }): any {
scrollY = props.height

return {
    table: VTable,
    body: {
      wrapper: VWrapper
    }
}
}

</code></pre><br><br>
来源:https://www.cnblogs.com/mapleChain/p/14110749.html
頁: [1]
查看完整版本: react antDesign hook 大数据表格虚拟滚动