You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
`;

关键改动说明

  1. 状态合并:将多个分散的布尔状态合并为selected和hovered两个字符串状态,分别记录当前选中和hover的字符ID,彻底避免状态不同步问题;
  2. 点击重置hover:点击按钮时主动将hovered设为null,强制清除原选中项的hover状态,解决未触发onMouseLeave导致的样式残留;
  3. 样式复用:通过styled-components的props传递状态和位置参数,统一按钮和图标的样式逻辑,删除大量重复的类选择器;
  4. 数据抽象:将字符数据抽象为数组,通过循环渲染按钮,后续扩展新字符只需修改数组即可,无需重复编写组件代码。

内容的提问来源于stack exchange,提问作者DgUZH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 04:30:43