React中onClick内操作DOM实现导航高亮:合规性与更优方案探讨
问题
我在React应用里做了个导航面板,用Link组件跳转不同页面,要高亮当前激活的导航链接。现在通过给激活链接加类实现了需求,代码如下:
import { Link } from "react-router-dom"; function Nav() { const highlight = (e) => { // Remove .nav-select class from all nav items let eles = document.querySelectorAll(".nav-select"); eles.forEach.call(eles, (ele) => { ele.classList.remove('nav-select'); }) // Add .nav-select to currently clicked nav item e.target.className = e.target.className + " nav-select" } return ( <nav className="main-nav"> <ul> <li onClick={highlight}> <Link to="/" >Home</Link> </li> <li onClick={highlight}> <Link to="/blog" >Blog</Link> </li> <li onClick={highlight}> <Link to="/clock" >Clock</Link> </li> <li onClick={highlight}> <Link to="/video" >Video</Link> </li> <li onClick={highlight}> <Link to="/goals" >Goals</Link> </li> </ul> </nav> ); }; export default Nav;
相关样式:
.main-nav .nav-select { border: 1px solid red; padding: 2px }
这个实现能达到效果,但我知道直接调用document修改DOM属于副作用,最佳实践是用useEffect处理。想问这只是最佳实践,还是不遵循会出问题?有没有更好的实现或优化方案?
回答
直接操作DOM不只是最佳实践问题,会引发实际bug:
- React的DOM diff算法可能会覆盖你手动修改的DOM状态,比如组件重新渲染时,手动添加的类可能被重置;
- 路由跳转不止点击链接这一种方式(比如浏览器前进/后退按钮、编程式导航),这时候你的onClick事件不会触发,高亮状态就会和实际路由不匹配。
最优方案:用React Router内置的
NavLink组件
NavLink是Link的增强版,会自动给当前匹配的路由链接添加激活类(默认是active,也可以自定义),完全不需要手动操作DOM。优化后的代码:
import { NavLink } from "react-router-dom"; function Nav() { return ( <nav className="main-nav"> <ul> <li> <NavLink to="/" className={({ isActive }) => isActive ? "nav-select" : ""} >Home</NavLink> </li> <li> <NavLink to="/blog" className={({ isActive }) => isActive ? "nav-select" : ""} >Blog</NavLink> </li> <li> <NavLink to="/clock" className={({ isActive }) => isActive ? "nav-select" : ""} >Clock</NavLink> </li> <li> <NavLink to="/video" className={({ isActive }) => isActive ? "nav-select" : ""} >Video</NavLink> </li> <li> <NavLink to="/goals" className={({ isActive }) => isActive ? "nav-select" : ""} >Goals</NavLink> </li> </ul> </nav> ); }; export default Nav;样式不需要修改,保持原来的
.main-nav .nav-select即可。如果想简化className的写法,也可以用模板字符串:className={({ isActive }) => `nav-item ${isActive ? "nav-select" : ""}`}自定义状态控制的方案(如果需要更灵活的逻辑):
如果不想用NavLink,可以用useLocation钩子获取当前路由路径,通过React状态来控制类名,避免直接操作DOM:import { Link, useLocation } from "react-router-dom"; function Nav() { const location = useLocation(); const getNavClass = (path) => { return location.pathname === path ? "nav-select" : ""; }; return ( <nav className="main-nav"> <ul> <li> <Link to="/" className={getNavClass("/")}>Home</Link> </li> <li> <Link to="/blog" className={getNavClass("/blog")}>Blog</Link> </li> <li> <Link to="/clock" className={getNavClass("/clock")}>Clock</Link> </li> <li> <Link to="/video" className={getNavClass("/video")}>Video</Link> </li> <li> <Link to="/goals" className={getNavClass("/goals")}>Goals</Link> </li> </ul> </nav> ); }; export default Nav;这种方式依赖React的状态更新,能保证路由变化时高亮状态同步,也不会和React的DOM管理冲突。
内容的提问来源于stack exchange,提问作者Niki
相关产品推荐
相关产品推荐

