/** * @file 各列宽度相关逻辑 * @desc 维护一份列宽度信息以对齐 head & body(fixHead: true)、计算 sticky 偏移(fixColumn: true)等 */ import React, { createContext, ReactNode, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useState } from 'react' import { addListener as addResizeListener, removeListener as removeResizeListener } from 'resize-detector' export type ColumnWidthContextValue = { containerWidth: number | null setContainer: (container: HTMLElement | null) => void widths: Record setWidth: (columnId: string, width: number) => void } const columnWidthCtx = createContext({ containerWidth: null, setContainer: () => undefined, widths: {}, setWidth: () => undefined }) export function useColumnWidth() { return useContext(columnWidthCtx) } // 单元格(Cell)中通过 `useColumnWidthReporter` 上报单元格宽度 export function useColumnWidthReporter(columnId: string, cellWrapper: HTMLTableCellElement | null) { const { containerWidth, setWidth } = useContext(columnWidthCtx) useLayoutEffect(() => { if (cellWrapper == null) return const syncWidth = () => setWidth(columnId, cellWrapper.getBoundingClientRect().width) syncWidth() addResizeListener(cellWrapper, syncWidth) return () => removeResizeListener(cellWrapper, syncWidth) // 这里特别地把 `containerWidth` 加到 deps 里,以实现在 container 宽度发生变化时 reset column 宽度信息 }, [containerWidth, columnId, cellWrapper, setWidth]) } export function ColumnWidthProvider({ children }: { children: ReactNode }) { const [widths, setWidths] = useState>({}) const setWidth = useCallback((columnId: string, width: number) => { setWidths(ws => ({ ...ws, [columnId]: width })) }, []) const [containerWidth, setContainerWidth] = useState(null) const [container, setContainer] = useState(null) useEffect(() => { if (container == null) return const handleContainerResize = () => { setContainerWidth(container.clientWidth) } addResizeListener(container, handleContainerResize) return () => removeResizeListener(container, handleContainerResize) }, [container]) const columnWidthCtxValue = useMemo( () => ({ containerWidth, setContainer, widths, setWidth }), [containerWidth, setContainer, widths, setWidth] ) return ( {children} ) }