
rc-table里Header、Footer、TableBody实现保持同频滚动的方法
场景:Header、Footer都有,Table设置了scrollX,才关注同频滚动
那么是如何实现的?
- 监听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>
获得当前正在执行的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如何固定左右两侧
场景:table的columns里设置fixed属性的时候,会出现滚动;fixed:true | 'left'固定左侧;fixed: 'right'固定右侧;
- 获取columns、columnWidths, 更新每个column的sticky的偏移距离;
- 更新涉及到fixed相关属性,fixedLeft、fixedRight、lastFixLeft、firstFixRight、lastFixRight、firstFixLeft、isSticky
相关链接:
 rc-table: https://github.com/react-component/table
 antd-table: https://ant.design/components/table-cn#components-table-demo-fixed-columns



















