/** 基础 hooks: useTip */const { useRef ,useState, useEffect, useCallback } = React;export interface TipState<T> { visible?: boolean; data?: T;}const useTip = <T>(initialState: T = {} as T) => { const [state, setState] = useState<TipState<T>>({ visible: false, data: initialState, }); const showTip = useCallback( (data: T) => { setState({ visible: true, data }); }, [setState], ); const hideTip = useCallback(() => { setState({ visible: false, data: initialState, }); }, [setState]); return [state, setState, { showTip, hideTip }] as const;};/** useMouseMove */const useMouseMove = ( ref: React.MutableRefObject<Element | null>, eventName: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions,) => { useEffect(() => { if (!ref?.current?.addEventListener) { console.log('未匹配到正确的属性'); return; } if (!eventName) { console.error('没有监听事件类型'); return; } ref.current.addEventListener(eventName, handler, options); return () => { ref.current?.removeEventListener(eventName, handler, options); }; }, [ref, eventName]);};/** usePosTip: 计算错误 */type OffsetPos = [number, number];interface PosState extends Omit<DOMRect, 'toJSON'> { offsetY?: number; offsetX?: number;}const initState: PosState = { x: 0, y: 0, top: 0, left: 0, right: 0, width: 0, bottom: 0, height: 0, /** 最终偏移量 */ offsetX: undefined, offsetY: undefined,};const initOffset: OffsetPos = [16, 24];const getContentRect = <T extends HTMLElement>(dom: T, pos: OffsetPos) => { const rect = dom.getBoundingClientRect() as PosState; const [x, y] = pos; const offsetX = rect.x + x; const offsetY = rect.y + y; return Object.assign(rect, { offsetX, offsetY, });};/** containerRef: 计算错误, 应使用 event.x 和 event.y */const usePosTip = <T extends HTMLElement>( containerRef: React.MutableRefObject<Element | null>, offsetPos = initOffset,) => { const ref = React.useRef<T | null>(null); const [state, setState, { showTip, hideTip }] = useTip<PosState>(initState); useMouseMove(containerRef, 'mousemove', event => { if (ref.current && containerRef.current) { const dom = containerRef.current; const contentDom = ref.current; const { clientHeight, clientWidth } = dom; const contentRect = getContentRect(contentDom, offsetPos); const { width, height, offsetX, offsetY } = contentRect; const newX = width + offsetX >= clientWidth ? clientWidth - offsetX + 10 : offsetX; const newY = height + offsetY >= clientHeight ? clientHeight - offsetY + 10 : offsetY; const rect = { ...contentRect, offsetY: newX, offsetX: newY, }; showTip(rect); } }); useMouseMove(containerRef, 'mouseout', hideTip); return [ref, state, setState, { showTip, hideTip }] as const;};const Demo = () => { const containerRef = useRef<HTMLDivElement | null>(null); const [ref, state, , { showTip, hideTip }] = usePosTip<HTMLSpanElement>(containerRef); return ( <div className="wrapper" ref={containerRef}> <pre ref={ref}>{JSON.stringify(state, null, 2)}</pre> </div> );};ReactDOM.render(<Demo />, mountNode);