React中onMouseEnter/onMouseLeave实现hover初始状态异常修复问询
解决按钮选中状态切换异常问题
问题描述
开发带hover效果的选择按钮时遇到以下问题:
- 按钮鼠标悬停时切换样式,已选中的按钮在其他按钮hover时保持选中样式;
- 组件首次渲染后直接点击未被鼠标划过的其他按钮,原选中按钮无法取消
active样式——原因是原按钮的onMouseLeave事件从未触发,其hover状态一直为true,导致hover状态 || 选中状态的判断始终成立,样式无法正常切换。
解决方案
核心问题是分散的状态变量导致状态不同步,通过合并状态管理、统一事件逻辑解决:
- 用单个状态管理选中项,避免多个布尔值的混乱;
- 用单个状态管理hover项,简化hover状态的重置逻辑;
- 点击时主动重置hover状态,确保原选中项的hover状态被清除;
- 复用样式逻辑,减少冗余代码。
修改后的代码
import { useState } from "react"; import styled from "styled-components"; // 抽象字符数据,便于扩展 const CHARACTERS = [ { id: 'choonsik', name: '춘식이', inactivePos: '0 17.647059%', activePos: '0 11.764706%' }, { id: 'ryan', name: '라이언', inactivePos: '0 88.235295%', activePos: '0 82.352941%' }, { id: 'apeach', name: '어피치', inactivePos: '0 5.882353%', activePos: '0 0%' }, ]; export default function App() { // 单个状态管理选中项 const [selected, setSelected] = useState('choonsik'); // 单个状态管理hover项 const [hovered, setHovered] = useState(null); const handleHover = (charId) => { setHovered(charId); }; const handleLeave = () => { setHovered(null); }; const handleClick = (charId) => { setSelected(charId); setHovered(null); // 点击时强制重置hover状态,解决未触发onMouseLeave的问题 }; return ( <Wrap> <div className="characterWrap"> <ul className="characterWrap__list"> {CHARACTERS.map((char) => ( <li key={char.id} className="characterWrap__card"> <CharacterButton isActive={hovered === char.id || selected === char.id} onMouseEnter={() => handleHover(char.id)} onMouseLeave={handleLeave} onClick={() => handleClick(char.id)} > <CharacterIcon isActive={hovered === char.id || selected === char.id} inactivePos={char.inactivePos} activePos={char.activePos} /> {char.name} </CharacterButton> </li> ))} </ul> </div> </Wrap> ); } const Wrap = styled.div` position: relative; width: 1000px; top: calc(50vh - 100px); .characterWrap { padding-bottom: 20px; margin: 0 auto; width: 100%; text-align: center; } .characterWrap__list { display: inline-flex; list-style: none; padding: 0; } .characterWrap__card { margin: 0 9px; text-align: center; } `; const CharacterButton = styled.div` color: ${props => props.isActive ? '#333' : '#999'}; font-size: 13px; position: relative; display: inline-block; cursor: pointer; `; const CharacterIcon = styled.div` background-position: ${props => props.isActive ? props.activePos : props.inactivePos}; background-size: 100%; display: block; transition: background-position 0.2s; margin-bottom: 5px; background-image: url(https://www.kakaofriendsgolf.com/img/v3_img_sprites_friends@3x.png); background-repeat: no-repeat; width: 70px; height: 70px; `;
关键改动说明
- 状态合并:将多个分散的布尔状态合并为
selected和hovered两个字符串状态,分别记录当前选中和hover的字符ID,彻底避免状态不同步问题; - 点击重置hover:点击按钮时主动将
hovered设为null,强制清除原选中项的hover状态,解决未触发onMouseLeave导致的样式残留; - 样式复用:通过styled-components的props传递状态和位置参数,统一按钮和图标的样式逻辑,删除大量重复的类选择器;
- 数据抽象:将字符数据抽象为数组,通过循环渲染按钮,后续扩展新字符只需修改数组即可,无需重复编写组件代码。
内容的提问来源于stack exchange,提问作者DgUZH
相关产品推荐
相关产品推荐

