You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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]:功能正常,但依赖缺失警告依然存在

问题根源

  1. useEffect 依赖错误:把内部声明的 uniqueParents 放进了依赖数组——这个变量每次渲染都会重新创建,直接导致 useEffect 反复触发
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 10:24:13