React中useEffect重复触发及树形节点展开需求解决问询
树形结构展开选中节点父节点:解决 useEffect 重复触发问题
场景
实现树形结构时,需要在树加载完成后,自动展开所有已选中子节点的父节点,保证这些父节点可见。
现有代码
获取数据(React Query)
const { data } = useGetData(); // 从接口获取树形数据的react-query hook
获取节点所有父节点的递归函数
const getAllParents = (key: string, parentNodes: string[]) => { Object.keys(data).forEach((itemKey: string) => { if ( data[itemKey]?.hasChildren && data[itemKey].children?.filter((child: string) => child === key).length ) { parentNodes.push(itemKey); getAllParents(itemKey, parentNodes); } }); return parentNodes; };
期望仅执行一次的展开逻辑(useEffect)
useEffect(() => { let allParents: string[] = []; let uniqueParents: string[] = []; selectedItems.forEach((item) => { const itemParents: string[] = getAllParents(item, []); allParents = [...allParents, ...itemParents]; uniqueParents = [...new Set(allParents)]; }); setExpandedItems(uniqueParents); }, [getAllParents, selectedItems, uniqueParents]);
遇到的问题
- 上述
useEffect会持续重复触发,达不到仅执行一次的预期 - 移除依赖里的
getAllParents能停止触发,但会出现 React Hook 依赖缺失的警告 - 尝试用
useCallback包裹getAllParents后,不同依赖数组的效果:- 空数组
[]:useEffect完全不执行,树形没展开 [getAllParents, selectedItems]:又开始持续触发[selectedItems]:功能正常,但依赖缺失警告依然存在
- 空数组
问题根源
useEffect依赖错误:把内部声明的uniqueParents放进了依赖数组——这个变量每次渲染都会重新创建,直接导致useEffect反复触发getAllParents的引用不稳定:函数依赖data,而data来自 React Query,每次重新 fetch 都会更新,导致useCallback返回新的函数引用,进而触发useEffect
解决方案
步骤1:修复 getAllParents 并稳定引用
用 useCallback 包裹函数,同时优化内部判断逻辑(用 includes 替代 filter.length,更高效):
const getAllParents = useCallback((key: string, parentNodes: string[]) => { Object.keys(data).forEach((itemKey: string) => { if ( data[itemKey]?.hasChildren && data[itemKey].children?.includes(key) ) { parentNodes.push(itemKey); getAllParents(itemKey, parentNodes); } }); return parentNodes; }, [data]);
步骤2:修正 useEffect 逻辑,确保仅执行一次
添加一个状态标记是否已经完成展开操作,避免重复触发:
import { useState, useEffect, useCallback } from 'react'; // 新增状态:标记是否已经执行过展开 const [hasDoneInitialExpand, setHasDoneInitialExpand] = useState(false); useEffect(() => { // 条件判断:数据加载完成、有选中项、且未执行过展开 if (!data || selectedItems.length === 0 || hasDoneInitialExpand) return; // 用flatMap简化数组拼接逻辑 const allParents = selectedItems.flatMap(item => getAllParents(item, [])); const uniqueParents = [...new Set(allParents)]; setExpandedItems(uniqueParents); setHasDoneInitialExpand(true); // 标记已完成,后续不再触发 }, [data, selectedItems, getAllParents, hasDoneInitialExpand]);
补充说明
- 如果后续需要在
selectedItems变化时重新展开,只需移除hasDoneInitialExpand相关的判断和状态即可 flatMap比forEach拼接数组更简洁高效,推荐使用
内容的提问来源于stack exchange,提问作者Leo Messi
相关产品推荐
相关产品推荐

