如何通过useEffect与resize事件替换数组现有值,避免持续追加
解决窗口Resize时React状态数组无法替换只能追加的问题
你这代码里有两个关键问题导致resize时状态只会往数组里追加值,没法替换:
- resize回调里用了
setStart((start) => [...start, newVal]),这是在原数组后面加新元素,不是替换现有内容 - 每次
shifts更新时都会给window加新的resize监听,旧监听没清理,不仅会重复触发,而且回调里捕获的hour、childPosStart都是初始渲染时的旧值,resize后DOM位置变了计算结果也不对
给你改好的代码,直接用就行:
useEffect(() => { // 把计算逻辑抽成函数,初始和resize时都能调用 const calculatePositions = () => { const hour = [...hourRef.current.children]; const parentPos = parentWidthRef.current.getBoundingClientRect(); const hourWidth = Math.round(hour[0].getBoundingClientRect().width); // 每次重新生成完整的start和end数组 const newStart = []; const newEnd = []; shifts.forEach((shift) => { const startHour = parseInt(shift.from.slice(0, 2)); const endHour = parseInt(shift.to.slice(0, 2)); const startMinute = parseInt(shift.from.slice(3, 5)); const endMinute = parseInt(shift.to.slice(3, 5)); const startEl = hour.find(h => h.getAttribute('data-id') === startHour.toString()); const endEl = hour.find(h => h.getAttribute('data-id') === endHour.toString()); // 加个判断防止找不到元素报错 if (startEl && endEl) { const childPosStart = startEl.getBoundingClientRect(); const childPosEnd = endEl.getBoundingClientRect(); newStart.push(Math.round((childPosStart.left - parentPos.left) + ((startMinute * hourWidth) / 60))); newEnd.push(Math.round((childPosEnd.left - parentPos.left) + ((endMinute * hourWidth) / 60))); } }); // 直接用新数组替换旧状态,不是追加 setStart(newStart); setEnd(newEnd); }; // 页面加载完先算一次初始位置 calculatePositions(); // 绑定resize监听 window.addEventListener('resize', calculatePositions); // 组件卸载或者shifts变化时,移除监听避免内存泄漏 return () => { window.removeEventListener('resize', calculatePositions); setStart([]); setEnd([]); }; }, [shifts]);
重点说下改了啥:
- 把位置计算逻辑单独拎出来,每次resize时重新获取最新的DOM位置和宽度,不会再用旧的闭包值
- 每次计算都生成全新的数组,直接替换掉原来的状态,彻底解决追加的问题
- 给resize监听加了清理逻辑,不会重复绑定导致多次触发
- 加了元素存在性判断,避免找不到对应hour元素时抛出错误
- 把时分的解析拆出来,代码可读性更好
内容的提问来源于stack exchange,提问作者Martin Avramov
相关产品推荐
相关产品推荐

