首先antd4的table的底层实现是rc-table,就从rc-table来看看。
场景:Table内容区域大于容器Table宽度,并且Table设置了scrollX,Header、Footer都有, 才关注同频滚动
那么是如何实现的?
const onScroll = (e: ScrollEvent) => {
// 拿到scrollLeft
const scrollLeft = e.target.scrollLeft
// 给所有的header、footer、table-body设置scrollLeft
header.scrollLeft = scrollLeft
footer.scrollLeft = scrollLeft
tableBody.scrollLeft = scrollLeft
}
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判断;
为什么多了一个入参、一个判断?继续往下看?
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;
<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;
}, []);
当然是监听onScroll事件;
给Tables设置scrollX的情况下,TableBody设置样式{overflow-x: auto}
这样会有同频滚动
<div
style={
...scrollXStyle,
...scrollYStyle
}
onScroll={onScroll}
ref={scrollBodyRef}
>
<TableComponent>
{bodyColGroup}
{bodyTable}
</TableComponent>
</div>
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];
}
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