You need to enable JavaScript to run this app. React实现类YouTube唯一投票点赞点踩按钮,解决点击后数值反复累加问题
优惠活动
大模型
产品
解决方案
定价
更多

React实现类YouTube唯一投票点赞点踩按钮,解决点击后数值反复累加问题

现有实现的核心问题
  • 组件未追踪用户的有效投票状态,点击事件触发时无前置校验,直接对计数做增减操作,导致连续点击时数值持续累加
  • 未实现YouTube类的投票互斥逻辑,和“单用户单次有效投票”的需求不匹配
  • 原实现未做已投票状态的UI提示,用户无法感知自己的投票状态
修复方案

核心逻辑是新增独立的投票状态追踪,所有计数操作都基于投票状态做分支判断,禁止无校验直接修改计数。

  1. 新增userVote状态,可选值为like/dislike/null(未投票),记录当前用户的投票选择
  2. 对点赞、点踩的点击逻辑做全分支覆盖:
    • 重复点击已投过的按钮:可选择实现取消投票(回退对应计数,状态重置为未投票),或直接拦截不执行操作
    • 从一方切换到另一方投票:回退上一次的计数调整,再执行新的投票计数,更新投票状态
    • 首次投票:直接执行对应计数调整,更新投票状态
  3. 根据投票状态给按钮加上高亮类,做已投票的视觉提示

修改后的完整组件代码

import React, { useState } from 'react';

const SingleMovieCard = ({singleMovie : {id, title, image ,category, likes, dislikes}, removeMovie}) => {
    // 初始化点赞、点踩的计数状态
    const [isLike, setIsLike] = useState(likes);
    const [isDislike, setIsDislike] = useState(dislikes);
    // 新增:追踪用户投票状态,可选值 'like' / 'dislike' / null
    const [userVote, setUserVote] = useState(null);

    const likeMovie = () => {
        // 已点赞:取消投票
        if (userVote === 'like') {
            setIsLike(prev => prev - 1);
            setUserVote(null);
            return;
        }
        // 之前点过踩:切换为点赞
        if (userVote === 'dislike') {
            setIsDislike(prev => prev + 1); // 回退之前的点踩计数
            setIsLike(prev => prev + 1);
            setUserVote('like');
            return;
        }
        // 首次点赞
        setIsLike(prev => prev + 1);
        setUserVote('like');
    }

    const dislikeMovie = () => {
        // 已点踩:取消投票
        if (userVote === 'dislike') {
            setIsDislike(prev => prev + 1);
            setUserVote(null);
            return;
        }
        // 之前点过赞:切换为点踩
        if (userVote === 'like') {
            setIsLike(prev => prev - 1); // 回退之前的点赞计数
            setIsDislike(prev => prev - 1);
            setUserVote('dislike');
            return;
        }
        // 首次点踩
        setIsDislike(prev => prev - 1);
        setUserVote('dislike');
    }

    return (
        <section key={id} className="single-movie">
            <img src={image} alt={title}/>
            <footer>
                <div className="movie-info">
                    <h2 className="title">{title}</h2>
                    <h4 className="category">{category}</h4>
                </div>  
            
                <div className="ratio-section">
                    <div>
                        <i 
                            onClick={likeMovie} 
                            className={`far fa-thumbs-up ${userVote === 'like' ? 'active' : ''}`}
                        ></i>
                        <h5>{isLike}</h5>
                    </div>
                    
                    <div>
                        <i 
                            onClick={dislikeMovie} 
                            className={`far fa-thumbs-down ${userVote === 'dislike' ? 'active' : ''}`}
                        ></i>
                        <h5>{isDislike}</h5>
                    </div>
                </div>

                <button className="btn" onClick={()=> removeMovie(id)}>Delete </button>
            </footer>
        </section>
    )
}

export default SingleMovieCard;
严格单次投票规则适配

如果需求要求用户投过票后无法修改、无法取消,只需要在likeMovie和dislikeMovie函数的最开头加入拦截逻辑:

// 已投票状态直接终止后续操作
if (userVote !== null) return;

加入该逻辑后,用户首次投票完成后,再次点击任何投票按钮都不会修改计数,完全符合“单用户一次有效投票”的要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.22 13:15:08