> 文章列表 > antd4里table的滚动是如何实现的?

antd4里table的滚动是如何实现的?

antd4里table的滚动是如何实现的?

antd4里table的滚动是如何实现的?
首先antd4的table的底层实现是rc-table,就从rc-table来看看。

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

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

那么是如何实现的?

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

二、 rc-table里的onScroll实现

先看一般的onScroll实现

  1. 监听onScroll获取scrollLeft
  2. 设置header、footer、tableBody的scrollLeft
    下面是伪代码哈
const onScroll = (e: ScrollEvent) => {// 拿到scrollLeftconst scrollLeft = e.target.scrollLeft// 给所有的header、footer、table-body设置scrollLeftheader.scrollLeft = scrollLeftfooter.scrollLeft = scrollLefttableBody.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设置scrollLeftscrollHeaderRef.current = mergedScrollLeft// body 设置scrollLeftscrollBodyRef.current = mergedScrollLeft}};

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

三、 Header、Footer的滚动监听

  1. 组件FixedHolder实现,给FixedHolder绑定ref;
  2. 监听的是onWheel, 不是onScroll;
    为什么监听onWheel不是onScroll?
React.useEffect(() => {function onWheel(e: WheelEvent) {// deltaX: Returns a double representing the horizontal scroll amountconst { 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;

  1. overflow:hidden
  2. 滚动条不存在

FixHolder组件

设置了样式overflow:hidden;

<divstyle={{overflow: 'hidden',...(isSticky ? { top: stickyTopOffset, bottom: stickyBottomOffset } : {}),}}ref={setScrollRef}className={classNames(className, {[stickyClassName]: !!stickyClassName,})}/><tablestyle={{tableLayout: 'fixed',visibility: noData || mergedColumnWidth ? null : 'hidden',}}>{(!noData || !maxContentScroll || allFlattenColumnsWithWidth) && (<ColGroupcolWidths={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}这样会有同频滚动

<divstyle={...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都是滚动,但是也有区别;并且在react里支持dom绑定onScroll、onWheel;

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