为鼠标事件添加防抖函数时,如何避免子组件重复渲染?
问题分析与解决方案
问题根源
原代码中,相同order_id的子组件不会重复渲染,是因为当调用handleMouseEnter设置相同的currentOrderId时,React会检测到状态值未变化,跳过状态更新,父组件不重新渲染,子组件自然也不会重复渲染。
添加防抖后出现重复渲染的原因有两个:
- 直接在
map中创建防抖函数(如onMouseEnter={() => debounce(...)}),每次父组件渲染时都会生成新的函数引用,子组件接收的props变化,触发重新渲染。 - 防抖函数未做状态判断,即使
order_id相同,延迟触发的防抖逻辑仍会重复调用setCurOrderId,导致父组件多次渲染,子组件随之重复渲染。
解决方案
方案一:缓存不同order_id对应的防抖函数
用useRef存储每个order_id的防抖函数实例,确保同一order_id始终拿到相同的函数引用,同时配合useCallback稳定函数引用,避免子组件无意义渲染。
import { useState, useRef, useCallback } from 'react'; import debounce from 'lodash/debounce'; const ParentComponent = () => { const [currentOrderId, setCurOrderId] = useState(null); // 存储不同order_id的防抖函数 const debounceEnterRef = useRef({}); // 获取对应order_id的防抖函数,缓存复用 const getDebouncedEnter = useCallback((orderId) => { if (!debounceEnterRef.current[orderId]) { debounceEnterRef.current[orderId] = debounce(() => { setCurOrderId(orderId); }, 300); } return debounceEnterRef.current[orderId]; }, []); // 缓存离开事件的防抖函数 const debouncedLeave = useCallback(debounce(() => { setCurOrderId(null); }, 300), []); return ( <Box> {data.map(item => { const { order_id } = item; return ( <GanttDiagramAction key={order_id} // 用order_id作为key,确保相同id的组件复用 onMouseEnter={getDebouncedEnter(order_id)} onMouseLeave={debouncedLeave} /> ); })} </Box> ); };
方案二:在防抖函数内判断状态是否需要更新
在防抖逻辑中新增判断,只有当order_id与当前currentOrderId不同时才更新状态,避免父组件无意义渲染,同时用useCallback稳定函数引用。
import { useState, useCallback } from 'react'; import debounce from 'lodash/debounce'; const ParentComponent = () => { const [currentOrderId, setCurOrderId] = useState(null); const handleMouseEnter = useCallback(debounce((orderId) => { // 仅当order_id不同时才更新状态 if (orderId !== currentOrderId) { setCurOrderId(orderId); } }, 300), [currentOrderId]); const handleMouseLeave = useCallback(debounce(() => { setCurOrderId(null); }, 300), []); return ( <Box> {data.map(item => { const { order_id } = item; return ( <GanttDiagramAction key={order_id} onMouseEnter={() => handleMouseEnter(order_id)} onMouseLeave={handleMouseLeave} /> ); })} </Box> ); };
额外优化:子组件使用memo
如果子组件GanttDiagramAction是纯组件,用memo包裹后,当props引用未变化时会跳过渲染,进一步提升性能:
import { memo } from 'react'; const GanttDiagramAction = memo(({ onMouseEnter, onMouseLeave }) => { // 子组件内部逻辑 });
内容的提问来源于stack exchange,提问作者David Abramov
相关产品推荐
相关产品推荐

