React中用useEffect/useRef检测指定class子元素时状态不更新如何解决
问题根因
你的状态不同步是三个问题叠加导致的:
useEffect依赖数组为空,仅在组件首次挂载时执行一次检测,后续点击链接、NavLink激活状态变更时,检测逻辑完全不会触发- 遍历子元素的逻辑有缺陷:每次循环都会调用
setOperationActive,最终状态会被最后一个子元素的判断结果覆盖,哪怕前面存在激活项,只要最后一个子元素未激活,状态就会被错误设为false - 你将
.accordion-content放在了条件渲染中,手风琴收起时DOM节点不存在,首次挂载如果处于收起状态,ref拿到的是null,检测直接失效。
最优解决方案
不要用DOM查询的方式判断激活状态,直接基于路由匹配实现,完全符合React声明式逻辑,不会出现同步问题:
- 从
react-router-dom引入useLocation钩子 - 维护当前手风琴分组下的所有路由路径,直接判断当前访问路径是否命中分组内路由即可
完整示例代码:
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
相关产品推荐
相关产品推荐

