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

React中hover时意外触发状态更新?Youtube API遭频繁调用

问题:鼠标Hover时意外触发Youtube API调用,造成资源浪费

我在获取Youtube URL的useEffect外部加了console.log(youtubeURL),发现只要鼠标移入或移出父div,就会触发URL获取操作,导致大量Youtube API调用被浪费。

原本应该只有点击React图标(FaPlay)时才会调用handlePlay函数,进而设置selectedTrack状态——这部分功能是正常的,但现在出现了hover触发的异常问题。

以下是我的代码:

const [hovered, setHovered] = useState(false);
const [selectedTrack, setSelectedTrack] = useState<{ artist: string, title: string } | null>(null)
const [youtubeURL, setYoutubeURL] = useState<string>('')

const handlePlay = (title: string, artist: string) => {
    setSelectedTrack({ title, artist });
    console.log('clicked')
};

useEffect(() => {
    const getSongURL = async () => {
        if (selectedTrack) 
        try {
            const encodedTitle = encodeURI(selectedTrack.title.toLowerCase());
            const encodedArtist = encodeURI(selectedTrack.artist.toLowerCase());
            const songURL = await axios.get(`http://localhost:8080/api/v1/songdata/req_song/${encodedTitle}%20${encodedArtist}`);
            setYoutubeURL(songURL?.data?.id)
        } catch (error) {
            console.log("Error finding album details:", error);
        }
    }
    getSongURL();
}, [selectedTrack])

console.log(youtubeURL)

return (
    <>
        <ul className='list-none m-0 py-0 px-[35px] relative'>
            <li className='box-border'>
                 <div className='flex justify-between items-center py-0 px-[15px]'>
                    {/* Inner details of block*/}
                        <div className={`group w-full flex justify-between box-border items-center rounded-lg hover:bg-[#4c426e] cursor-pointer self-stretch min-w-0 h-[50px] p-0 mb-2 relative`} 
                            onMouseEnter={() => setHovered(true)}
                            onMouseLeave={() => setHovered(false)}>
                            <div className={`w-full text-[12px] min-w-[35px] max-w-[35px] text-left ml-2 self-center shrink text-lg text-[#989898] relative`}>
                                <div className={`absolute top-0 left-0 w-full h-full flex items-center justify-center transition-all ease-in-out duration-300`}>
                                    <span style={{ opacity: hovered ? 0 : 100 }} className={`absolute top-0 left-0 w-full h-full flex items-center justify-center transition-all ease-in-out duration-300`} >
                                        {number}
                                    </span>
                                    <span style={{ opacity: hovered ? 100 : 0 }} className={`absolute bottom-0 left-0 w-full h-full flex items-center justify-center transition-all ease-in-out duration-300`} >
                                        <FaPlay onClick={() => handlePlay(title, artist)}/>
                                    </span>
                                </div>
                            </div>
                            </div>
                        </li>
                    </ul>           
    </>
)
}

我感觉onMouseEnter和onMouseLeave命令似乎对其产生了影响,但onClick事件本不该关联hover操作,为何仅hover就会持续调用handlePlay函数呢?


问题原因与解决方案

核心原因

你的代码里,FaPlay图标所在的span是绝对定位的,hover时会从透明变为可见。当鼠标hover父div时,这个span会覆盖原来显示{number}的span区域——如果鼠标在hover过程中轻微移动,或者元素切换透明度时触发了浏览器的点击误判,就会意外触发FaPlay的onClick事件,调用handlePlay修改selectedTrack,进而触发useEffect里的API调用。

另外,你用useState管理hovered状态,每次hover都会触发组件重新渲染,也增加了误触的概率。

解决方案

1. 用CSS替代state管理hover状态(推荐)

直接用Tailwind的group-hover类实现图标切换,完全不需要hovered状态和鼠标事件监听,从根源减少组件渲染和误触可能:

// 删掉hovered相关的state、onMouseEnter/onMouseLeave
return (
    <>
        <ul className='list-none m-0 py-0 px-[35px] relative'>
            <li className='box-border'>
                 <div className='flex justify-between items-center py-0 px-[15px]'>
                    {/* Inner details of block*/}
                        <div className={`group w-full flex justify-between box-border items-center rounded-lg hover:bg-[#4c426e] cursor-pointer self-stretch min-w-0 h-[50px] p-0 mb-2 relative`}>
                            <div className={`w-full text-[12px] min-w-[35px] max-w-[35px] text-left ml-2 self-center shrink text-lg text-[#989898] relative`}>
                                <span className={`absolute top-0 left-0 w-full h-full flex items-center justify-center transition-all ease-in-out duration-300 group-hover:opacity-0`}>
                                    {number}
                                </span>
                                <span className={`absolute top-0 left-0 w-full h-full flex items-center justify-center transition-all ease-in-out duration-300 opacity-0 group-hover:opacity-100`}>
                                    <FaPlay onClick={() => handlePlay(title, artist)}/>
                                </span>
                            </div>
                        </div>
                    </li>
                </ul>           
    </>
)

2. 阻止点击事件冒泡,避免误触

如果不想修改hover逻辑,给FaPlay的点击事件添加阻止冒泡,避免父元素的事件干扰:

<FaPlay onClick={(e) => {
  e.stopPropagation();
  handlePlay(title, artist);
}}/>

3. 给API调用加防抖,减少资源浪费

如果担心还有其他误触场景,可以给API调用加防抖,确保短时间内只执行一次:

import { debounce } from 'lodash';
import { useCallback } from 'react';

// 组件内定义防抖函数
const getSongURL = useCallback(debounce(async (track) => {
  if (!track) return;
  try {
    const encodedTitle = encodeURI(track.title.toLowerCase());
    const encodedArtist = encodeURI(track.artist.toLowerCase());
    const songURL = await axios.get(`http://localhost:8080/api/v1/songdata/req_song/${encodedTitle}%20${encodedArtist}`);
    setYoutubeURL(songURL?.data?.id);
  } catch (error) {
    console.log("Error finding album details:", error);
  }
}, 500), []);

useEffect(() => {
  if (selectedTrack) {
    getSongURL(selectedTrack);
  }
}, [selectedTrack, getSongURL]);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 07:58:00