如何重构MUI Tooltip封装组件以减少代码重复?
优化后的代码实现
import { TooltipProps } from '@mui/material'; import { useRef, useState, useCallback } from 'react'; import { Tooltip } from '../tooltip/Tooltip'; interface TableTooltipProps extends TooltipProps { selector?: string; } export function TableTooltip({ selector = '.MuiTableContainer-root', children, ...props }: TableTooltipProps) { const [isScrolling, setIsScrolling] = useState(false); const timeoutIdRef = useRef<number | null>(null); const elementRef = useRef<HTMLTableCellElement>(null); const tableElementRef = useRef<HTMLElement | null>(null); // 统一获取表格元素,仅初始化一次 const getTableElement = useCallback(() => { if (!tableElementRef.current && elementRef.current) { tableElementRef.current = elementRef.current.closest(selector) as HTMLElement; } return tableElementRef.current; }, [selector]); const handleScroll = useCallback(() => { console.warn('scrolling'); setIsScrolling(true); const tableElement = getTableElement(); tableElement?.removeEventListener('scroll', handleScroll); console.warn('aborted'); if (timeoutIdRef.current) { clearTimeout(timeoutIdRef.current); } timeoutIdRef.current = window.setTimeout(() => { setIsScrolling(false); }, 500); }, [getTableElement]); const attachToScrollEvent = useCallback(() => { const tableElement = getTableElement(); if (tableElement) { console.warn('added'); tableElement.addEventListener('scroll', handleScroll); } }, [getTableElement, handleScroll]); const detachFromScrollEvent = useCallback(() => { const tableElement = getTableElement(); if (tableElement) { console.warn('removed'); tableElement.removeEventListener('scroll', handleScroll); } }, [getTableElement, handleScroll]); return !isScrolling ? ( <> <Tooltip onOpenCallback={attachToScrollEvent} onCloseCallback={detachFromScrollEvent} {...props} > {children} </Tooltip> <td className="html-element-dummy" style={{ display: 'none' }} ref={elementRef} /> </> ) : ( <> {children} </> ); }
核心优化点说明
- 复用表格元素引用:新增
tableElementRef缓存表格DOM节点,通过getTableElement统一获取逻辑,避免在三个回调中重复调用closest(selector) - 消除重复判断:移除了每个函数中重复的
ref.current检查,统一在getTableElement中处理 - 优化定时器存储:将
timeoutId从useState改为useRef,因为定时器ID无需触发组件重渲染,用ref更高效且避免状态不同步 - 缓存回调函数:用
useCallback包裹所有事件回调,避免组件每次渲染生成新函数实例,减少不必要的事件解绑/绑定操作 - 类型安全提升:给元素ref指定明确的HTML类型,替换原有的
any类型,增强代码健壮性
内容的提问来源于stack exchange,提问作者BingeCode
相关产品推荐
相关产品推荐

