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

React中用useEffect/useRef检测指定class子元素时状态不更新如何解决

问题根因

你的状态不同步是三个问题叠加导致的:

  • useEffect依赖数组为空,仅在组件首次挂载时执行一次检测,后续点击链接、NavLink激活状态变更时,检测逻辑完全不会触发
  • 遍历子元素的逻辑有缺陷:每次循环都会调用setOperationActive,最终状态会被最后一个子元素的判断结果覆盖,哪怕前面存在激活项,只要最后一个子元素未激活,状态就会被错误设为false
  • 你将.accordion-content放在了条件渲染中,手风琴收起时DOM节点不存在,首次挂载如果处于收起状态,ref拿到的是null,检测直接失效。
最优解决方案

不要用DOM查询的方式判断激活状态,直接基于路由匹配实现,完全符合React声明式逻辑,不会出现同步问题:

  1. 从react-router-dom引入useLocation钩子
  2. 维护当前手风琴分组下的所有路由路径,直接判断当前访问路径是否命中分组内路由即可

完整示例代码:

import { React, useRef, useEffect, useState } from 'react';
import { NavLink, useLocation } from 'react-router-dom';

export function App(props) {
  const accordionOperation = useRef(null);
  const [isOperationOpen, setOperationOpen] = useState(false);
  const location = useLocation();

  // 定义当前手风琴分组下的所有路由
  const operationRoutes = [
    "/hc20/bee",
    "/hc20/command",
    "/hc20/company",
    "/hc20/dashboard",
    "/hc20/device-profile",
    "/hc20/location",
    "/hc20/route",
    "/hc20/sub",
    "/hc20/trackable",
    "/hc20/user"
  ];

  // 实时判断当前分组是否有激活项
  const isOperationActive = operationRoutes.some(path => 
    location.pathname.startsWith(path)
  );

  // 可选:访问分组内路由时自动展开手风琴
  useEffect(() => {
    if (isOperationActive && !isOperationOpen) {
      setOperationOpen(true);
    }
  }, [isOperationActive, isOperationOpen]);

  return (
    <div className='App'>
      <div className="accordion operations">
        <div className="accordion-item">
          <div
            className="accordion-title"
            role="button"
            tabIndex={0}
            onClick={() => setOperationOpen(!isOperationOpen)}
          >
            <div className="accordion-title-head">
              <ArrowsHorizontal style={{ paddding: "4px" }} />
              <div className="dropdownOperation">Operations</div>
              <div className="text-white">
                {isOperationOpen ? <ChevronUp /> : <ChevronDown />}
              </div>
            </div>
          </div>
          {isOperationOpen && (
            <ul className="accordion-content" ref={accordionOperation}>
              <NavLink
                to="/hc20/bee"
                className={({ isActive }) => (isActive ? "link-active" : "disable")}
              >
                <li className="aside_list-item">Bees</li>
              </NavLink>
              <NavLink
                to="/hc20/command"
                className={({ isActive }) => (isActive ? "link-active" : "disable")}
              >
                <li className="aside_list-item">Commands</li>
              </NavLink>
              <NavLink
                to="/hc20/company"
                className={({ isActive }) => (isActive ? "link-active" : "disable")}
              >
                <li className="aside_list-item">Companies</li>
              </NavLink>
              <NavLink
                to="/hc20/dashboard"
                className={({ isActive }) => (isActive ? "link-active" : "disable")}
              >
                <li className="aside_list-item">Dashboards</li>
              </NavLink>
              <NavLink
                to="/hc20/device-profile"
                className={({ isActive }) => (isActive ? "link-active" : "disable")}
              >
                <li className="aside_list-item">Device Profiles</li>
              </NavLink>
              <NavLink
                to="/hc20/location"
                className={({ isActive }) => (isActive ? "link-active" : "disable")}
              >
                <li className="aside_list-item">Location</li>
              </NavLink>
              <NavLink
                to="/hc20/route"
                className={({ isActive }) => (isActive ? "link-active" : "disable")}
              >
                <li className="aside_list-item">Routes</li>
              </NavLink>
              <NavLink
                to="/hc20/sub"
                className={({ isActive }) => (isActive ? "link-active" : "disable")}
              >
                <li className="aside_list-item">Subscriptions</li>
              </NavLink>
              <NavLink
                to="/hc20/trackable"
                className={({ isActive }) => (isActive ? "link-active" : "disable")}
              >
                <li className="aside_list-item">Trackables</li>
              </NavLink>
              <NavLink
                to="/hc20/user"
                className={({ isActive }) => (isActive ? "link-active" : "disable")}
              >
                <li className="aside_list-item">Users</li>
              </NavLink>
            </ul>
          )}
        </div>
      </div>
    </div>
  );
}
备选方案:DOM监听实现

如果你不想维护路由列表,坚持用DOM检测的方式,需要用MutationObserver监听子元素的class变化,同时修正遍历逻辑:

useEffect(() => {
  const checkActiveState = () => {
    const container = accordionOperation.current;
    if (!container) {
      setOperationActive(false);
      return;
    }
    // 只要存在一个带link-active类的子元素就返回true,避免循环中重复setState
    const hasActiveItem = Array.from(container.children).some(item => 
      item.classList.contains("link-active")
    );
    setOperationActive(hasActiveItem);
  };

  // 首次执行检测
  checkActiveState();

  const container = accordionOperation.current;
  if (!container) return;

  // 监听容器内所有子元素的class属性变化
  const observer = new MutationObserver(checkActiveState);
  observer.observe(container, {
    subtree: true,
    attributes: true,
    attributeFilter: ['class']
  });

  // 组件卸载时移除监听
  return () => observer.disconnect();
}, [isOperationOpen]);

该方案属于命令式DOM操作,不符合React的声明式编程范式,仅作为备选方案使用。

内容的提问来源于stack exchange,提问作者sayinmehmet47

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:39:15