React useEffect依赖错误致函数重复执行7次问题排查
React DropdownMultiSelect 组件重复执行问题修复方案
核心问题原因
updateAddedState初始渲染阶段执行7次是依赖配置错误、冗余包装、无效逻辑共同导致的:
- 占位符更新的
useEffect错误将自身修改的placeholderValue加入依赖数组,触发「状态修改→effect触发→再次修改状态」的循环渲染链 - 触发外部状态更新的
useEffect同时依赖updateAddedState和addedItems,而updateAddedState被useCallback包裹时依赖父组件传入的updateState,如果父组件未对updateState做引用稳定处理,每次父组件重渲染都会生成新的updateState,连带触发当前组件effect重复执行 - 代码中
isMounted判断是无效逻辑:变量在每次effect执行时都会重新初始化,根本无法拦截卸载后的执行 addItem/removeItem方法采用手动数组拷贝、splice的写法,同时依赖闭包中旧的addedItems值,容易触发意外重渲染,且手动调用handleFilterData产生了冗余的状态更新handleFilterData用useCallback包装后作为useEffect依赖,只要addedItems/data变化就会生成新的函数引用,额外触发effect执行
可落地修复方案
- 修正所有useEffect的依赖项,移除自引用、冗余依赖
- 去掉不必要的
useCallback包装,减少引用变化带来的意外触发 - 用函数式状态更新替代闭包取值,避免旧值导致的逻辑异常
- 删除无效的
isMounted判断和冗余的手动方法调用
核心修改后的代码片段
1. 修正占位符更新逻辑
// 移除依赖数组中的placeholderValue,避免自触发循环 useEffect(() => { if (addedItems.length === 1) { setPlaceholderValue(`${addedItems.length} vald`); } else if (addedItems.length > 1) { setPlaceholderValue(`${addedItems.length} valda`); } else { setPlaceholderValue(placeholder); } }, [addedItems, placeholder]);
2. 简化过滤逻辑,移除冗余useCallback包装
// 删掉外层单独定义的handleFilterData useCallback,直接将逻辑写入effect useEffect(() => { const newFilter = data .filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())) .filter(item => !addedItems.some(added => added.id === item.id)); setFilteredData(newFilter); }, [searchTerm, addedItems, data]);
3. 修正外部状态同步逻辑,仅在addedItems变化时触发
// 删掉外层单独定义的updateAddedState useCallback,移除无效isMounted判断 useEffect(() => { console.log("running"); updateState(addedItems); // 注意:如果父组件传入的updateState引用不稳定(未做useCallback包装),需要将updateState加入本effect的依赖数组 }, [addedItems]);
4. 修正增删选项的状态更新逻辑
// 用函数式更新拿最新状态,移除冗余的手动过滤调用 const addItem = (value: IData) => { setAddedItems(prev => [...prev, value]); setSearchTerm(""); setInputActive(false); }; // 简化数组过滤逻辑,避免双层循环+splice的下标偏移问题 const removeItem = (value: IData, e: React.MouseEvent) => { e.preventDefault(); setAddedItems(prev => prev.filter(item => item.id !== value.id)); setInputActive(true); };
优化后效果
- 初始渲染阶段
console.log("running")仅执行1次,无无效重复触发 - 只有
addedItems实际发生增删变更时,才会调用父组件传入的updateState方法 - 去掉冗余包装代码后,整体逻辑更简洁,重渲染次数大幅降低
内容的提问来源于stack exchange,提问作者Jesper Karlsson
相关产品推荐
相关产品推荐

