React onClick点击任意项始终仅作用于列表最后一个元素问题
问题描述
开发React导航栏组件时,在Navbar组件内绑定onClick事件后,点击导航列表任意元素,事件始终只作用于列表最后一个元素,所有点击操作都只会修改最后一个元素的样式。
涉及的原有代码如下:
Navbar组件原有代码
import './Navbar.scss'; import ListItem from '../ListItem/ListItem'; import { navLinks } from '../../staticData'; import { useRef } from 'react'; const Navbar = () => { const itemRef = useRef(); const handleClick = () => { itemRef.current.style.background = 'red' }; return ( <div className='navbar'> <ul className="navbarList"> { navLinks.map((item, index) => ( <ListItem item={item} key={index} onClick={handleClick} itemRef={itemRef}/> )) } </ul> </div> ) } export default Navbar
ListItem组件原有代码
import './ListItem.scss'; import { Link } from 'react-router-dom'; const ListItem = ({ item, onClick, itemRef }) => { return ( <Link onClick={onClick} ref={itemRef} className='listItemWrapper' to=''> <li className="listItem">{item.name}</li> </Link> ) } export default ListItem
问题根因
你在Navbar组件中只创建了单个ref实例,map循环渲染所有ListItem时,所有列表项都绑定了这同一个ref。组件挂载阶段,每个绑定该ref的DOM节点会依次赋值给itemRef.current,循环结束后itemRef.current只会保留最后一个渲染的列表项DOM引用,因此无论点击哪个导航项,事件触发时修改的永远是最后一个元素的样式。
修复方案
方案1:状态驱动样式(官方推荐写法)
React本身是数据驱动视图的框架,不推荐直接操作DOM修改样式,完全可以通过组件状态记录当前激活的导航项,动态匹配样式类实现需求,可维护性更强:
修改后的Navbar组件
import './Navbar.scss'; import ListItem from '../ListItem/ListItem'; import { navLinks } from '../../staticData'; import { useState } from 'react'; const Navbar = () => { // 记录当前激活的导航项索引,默认可设置首个导航项为激活态 const [activeIndex, setActiveIndex] = useState(0); const handleClick = (index) => { setActiveIndex(index); }; return ( <div className='navbar'> <ul className="navbarList"> { navLinks.map((item, index) => ( <ListItem item={item} key={index} onClick={() => handleClick(index)} isActive={activeIndex === index} /> )) } </ul> </div> ) } export default Navbar
修改后的ListItem组件
根据父组件传入的激活标识,动态拼接样式类,不需要接收ref:
import './ListItem.scss'; import { Link } from 'react-router-dom'; const ListItem = ({ item, onClick, isActive }) => { return ( <Link onClick={onClick} className={`listItemWrapper ${isActive ? 'active' : ''}`} to='' > <li className="listItem">{item.name}</li> </Link> ) } export default ListItem
补充样式
在ListItem.scss中新增激活态样式即可:
.listItemWrapper.active { background: red; }
方案2:ref数组存储DOM引用(特殊场景使用)
如果业务场景必须直接操作DOM,不要使用单个ref绑定所有列表项,初始化ref数组存储每个导航项的独立DOM引用,点击时传入索引定位到对应DOM操作即可:
修改后的Navbar组件
import './Navbar.scss'; import ListItem from '../ListItem/ListItem'; import { navLinks } from '../../staticData'; import { useRef } from 'react'; const Navbar = () => { // 初始化ref数组,长度和导航列表一致 const itemRefs = useRef([]); const handleClick = (index) => { // 先重置所有项的样式,避免多个项同时保留红色背景 itemRefs.current.forEach(ref => { if(ref) ref.style.background = ''; }); // 仅修改当前点击项的样式 itemRefs.current[index].style.background = 'red'; }; return ( <div className='navbar'> <ul className="navbarList"> { navLinks.map((item, index) => ( <ListItem item={item} key={index} onClick={() => handleClick(index)} // 将每个列表项DOM存入ref数组对应索引位置 itemRef={el => itemRefs.current[index] = el} /> )) } </ul> </div> ) } export default Navbar
ListItem组件保持原有逻辑无需修改即可正常运行。
内容的提问来源于stack exchange,提问作者user17969172
相关产品推荐
相关产品推荐

