React中滚动时为组件添加/移除active类的实现方法
React实现滚动时侧边栏菜单项高亮
核心思路
放弃直接操作DOM的原生JS逻辑,改用React的状态管理+props传递控制侧边栏的active类,同时通过useEffect监听滚动事件,计算当前可视区域对应的分类ID。
步骤1:在父组件中维护激活状态并监听滚动
假设<SideMenu/>和<mainContainer/>都在同一个父组件(比如CategoryPage)中,这里统一处理滚动逻辑和状态:
import { useState, useEffect, useRef } from 'react'; import SideMenu from './SideMenu'; import MainContainer from './MainContainer'; const CategoryPage = () => { const [activeCategory, setActiveCategory] = useState('category-1'); // 缓存分类section的DOM引用,避免每次滚动重复查询DOM const categorySectionsRef = useRef([]); // 滚动事件处理函数 const handleScroll = () => { categorySectionsRef.current.forEach(section => { if (!section) return; const rect = section.getBoundingClientRect(); // 当section顶部距离视窗顶部小于75px且未完全移出视窗时,更新激活分类 if (rect.top < 75 && rect.bottom > 0) { setActiveCategory(section.id); } }); }; useEffect(() => { window.addEventListener('scroll', handleScroll, true); // 组件卸载时移除监听,避免内存泄漏 return () => { window.removeEventListener('scroll', handleScroll, true); }; }, []); return ( <div className="row"> <div className="col-lg-3 pr-0"> <SideMenu activeCategory={activeCategory} /> </div> <div className="col-lg-6 category-products-section"> {/* 给每个category-section添加ref,存入数组 */} <div className="category-section" id="category-1" ref={el => categorySectionsRef.current[0] = el} > {/* 原有内容 */} </div> <div className="category-section" id="category-2" ref={el => categorySectionsRef.current[1] = el} > {/* 原有内容 */} </div> {/* 更多分类section... */} </div> </div> ); }; export default CategoryPage;
步骤2:修改SideMenu组件,根据props控制active类
假设<SideMenu/>包含多个菜单项,每个项的name对应分类section的id:
const SideMenu = ({ activeCategory }) => { // 菜单项数据可根据实际情况定义,这里示例为数组 const menuItems = [ { name: 'category-1', label: 'Festive Offer Best Ever' }, { name: 'category-2', label: 'Another Category' }, // 更多菜单项... ]; return ( <div className="categories-sidebar"> {menuItems.map(item => ( <div key={item.name} className={`categories-sidebar-item ${item.name === activeCategory ? 'active' : ''}`} name={item.name} > {item.label} </div> ))} </div> ); }; export default SideMenu;
关键说明
- 避免直接操作DOM:React虚拟DOM会管理DOM更新,直接用
document.querySelector修改类名可能导致状态不一致,用useRef缓存DOM节点是更安全的方式。 - 状态提升:激活状态需要在两个组件间共享,因此将状态提升到共同父组件,再通过props传递给SideMenu。
- 清理监听:在
useEffect返回函数中移除滚动监听,防止组件卸载后仍执行回调造成内存泄漏。 - 性能优化:如果分类数量较多,可使用节流函数(如
lodash.throttle)包装handleScroll,减少滚动时的函数调用次数。
内容的提问来源于stack exchange,提问作者Krish Varma
相关产品推荐
相关产品推荐

