antd4里table滚动的实现

2023-03-02 11:35:59

目录一、rc-table里Header、Footer、TableBody实现保持同频滚动的方法二、rc-table里的onScroll实现三、Header、Footer的滚动监听四、TableBo...

目录
一、rc-table里Header、Footer、TableBody实现保持同频滚动的方法
二、 rc-table里的onScroll实现
三、 Header、Footer的滚动监听
四、TableBody的滚动
五、很nice的点
相关链接:

首先antd4的table的底层实现是rc-table,就从rc-table来看看。

一、rc-table里Header、Footer、TableBody实现保持同频滚动的方法

场景:Table内容区域大于容器Table宽度,并且Table设置了scrollX,Header、Footer都有, 才关注同频滚动

那么是如何实现的?

监听onScroll方法获取到滚动条向左的滚动的距离scrollLeft;
同时给三个dom设置scrollLeft

二、 rc-table里的onScroll实现

先看一般的onScroll实现

监听onScroll获取scrollLeft
设置header、footer、tableBody的scrollLeft

下面是伪代码哈

const onScroll = (e: ScrollEvent) => {
    // 拿到scrollLeft
    const scrollLeft = e.target.scrollLeft
    // 给所有的header、footer、table-body设置scrollLeft
    header.scrollLeft = scrollLeft
    footer.scrollLeft = scrollLeft
    tableBody.scrollLeft = scrollLeft
}

源码里onScroll的实现

const onScroll = ({
  currentTarget,
  scrollLeft,
 }: {
  currentTarget: htmlElement;
  scrollLeft?: number;
 }) => {
  const mergedScrollLeft = typeof scrollLeft === 'number' ? scrollLeft : currentTarget.scrollLeft;

  const compareTarget = currentTarget || EMPTY_SCROLL_TARGET;
  if (!getScrollTarget() || getScrollTarget() === compareTarget) {
   setScrollTarget(compareTarget);
   //一个 滚动需要 控制 header、body、summary、stickyScrollBar所有同步滚动
   // header设置scrollLeft
   scrollHeaderRef.current = mergedScrollLeft
   // body 设置scrollLeft
   scrollBodyRef.current = mergedScrollLeft
  }
 };

 对比两个的实现,可以看到rc-table里的实现多了一个入参scrollLeft和一个if判断;
为什么多了一个入参、一个判断?继续往下看?

三、 Header、Footer的滚动监听

用组件FixedHolder实现,给FixedHolder绑定ref;
监听的是onWheel, 不是onScroll;

为什么监听onWheel不是onScroll?

React.useEffect(() => {
   function onWheel(e: WheelEvent) {
    // deltaX: Returns a double representing the horizontal scroll amount
    const { currentTarget, deltaX } = e as unknown as React.WheelEvent<HTMLDivElement>;
    // 避免触发不必要滚动, 是一种优化
    if (deltaX) {
     onScroll({ currentTarget, scrollLeft: currentTarget.scrollLeft + deltaX });
     e.preventDefault();
    }
   }
   fixHolder.current?.addEventListener('wheel', onWheel);

   return () => {
    fixHolder.current?.removeEventListener('wheel', onWheel);
   };
  }, []);

不要将 onscroll 与 onwheel混淆。onwheel 是鼠标滚轮旋转,而 onscroll 处理的是对象内部内容区的滚动事件。
当dom满足下面任意一条的时候,不会触发onScroll;

overflow:hidden
滚动条不存在

FixHolder组件

设置了样式overflow:hidden;

<div
        style={{
          overflow: 'hidden',
          ...(isSticky ? { top: stickyTopOffset, bottom: stickyBottomOffset } : {}),
        }}
        ref={setScrollRef}
        className={classNames(className, {
          [stickyClassName]: !!stickyClassName,
        })}
                />
                <table
          style={{
            tableLayout: 'fixed',
            visibility: noData || mergedColumnWidth ? null : 'hidden',
          }}
        >
          {(!noData || !maxContentScroll || allFlattenColumnsWithWidth) && (
            <ColGroup
              colWidths={mergedColumnWidth ? [...mergedColumnWidth, combinationScrollBarSize] : []}
              columCount={columCount + 1}
              columns={flattenColumnsWithScrollbar}
            />
          )}
          {children({
            ...props,
            stickyOffsets: headerStickyOffsets,
            columns: columnsWithScrollbar,
            flattenColumns: flattenColumnsWithScrollbar,
          })}
        </table>
                </div>

通过ref,调用useCallback赋值dom;利用scrollRef.current监听wheel事件,转成onScroll,增加入参scrollLeft;

const setScrollRef = React.useCallback((element: HTMLElement) => {
      scrollRef.current = element;
    }, []);

四、TableBody的滚动

当然是监听onScroll事件;
给Tables设置scrollX的情况下,TableBody设置样式{overflow-x: auto}这样会有同频滚动

<div
  style={
    ...scrollXStyle,
    ...scrollYStyle
  }
          onScroll={onScroll}
          ref={scrollBodyRef}
        >
          <TableComponent>
            {bodyColGroup}
            {bodyTable}
          </TableComponent>
        </div>

五、很nice的点

获得当前正在执行的dom

const [setScrollTarget, getScrollTarget] = useTimeoutLock(null);

getScrollTarget用来获得当前正在执行的dom
使用useState来存储正在执行的dom; 当组件重新渲染,dom更新,此时正在执行的dom,在下一个render的时候,就变了;useRef在下一次渲染之前不重新赋值,还是保留和上一次一样的值;
源码里使用useRef + setTimeout实现;useRef是用来存放当前正在执行的dom;setTimeout用来节流;
其中getState获取正在执行当前state,可能是空的;setState设置当前的State,并且在100ms以后清空设置的状态;

export function useTimeoutLock<State>(defaultState?: State): [(state: State) => void, () => State | null] {
 const frameRef = useRef<State | null>(defaultState || null);
 const timeoutRef = useRef<number>();

 function cleanUp() {
  window.clearTimeout(timeoutRef.current);
 }

 function setState(newState: State) {
  frameRef.current = newState;
  // 清空上一次的定时器
  cleanUp();
 
  timeoutRef.current = window.setTimeout(() => {
   frameRef.current = null;
   timeoutRef.current = undefined;
  }, 100);
 }

 function getState() {
  return frameRef.current;
 }

 useEffect(() => cleanUp, []);

 return [setState, getState];
}

onScroll为什么设置if判断

getScrollTarget()调用onScroll的之前,是否有滚动的dom; 没有就更新;有,判断是否和触发onScroll是相同Dom;是,更新;目的是为了避免执行上一个onScroll的时候,下一个onScroll执行,陷入循环,就相当于节流了;hooks版本的节流

const compareTarget = currentTarget || EMPTY_SCROLL_TARGET; 
// 固定滚动项
// 在处理上一个滚动的时候,禁止下一个也滚动执行onScroll
if (!getScrollTarget() || getScrollTarget() === compareTarget) {
    setScrollTarget(compareTarget);
}

看这块逻辑的时候,优化细节;从hooks的角度,实现节流;wheel和scroll都是滚动,但是也有区别;并且在reaphpct里支持dom绑定onScroll、onWheel;

相关链接:

rc-table: https://github.com/react-component/table
antd-table: https://ant.design/components/table-cn#components-table-demo-fixed-columns

到此这篇关于antd4里table滚动的实现的文章就介绍到这了,更多相关antd4 table滚动内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!