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

如何实现类似Instagram的防重复点赞前端功能?

问题

需要实现类似Instagram的点赞系统:已登录用户点击照片可切换点赞/取消点赞状态,状态由服务端存储,页面刷新、用户登出或清除缓存后不影响状态。后端接口返回数据正确,但当前前端代码无法正确同步状态,需完成最终实现。

服务端返回数据结构:

  • 初始加载时单张照片数据:
{
  "url": "someUrl.com",
  "likes": 161,
  "UserID": "u-79696bbe-18e5-4e73-bfb7-e8c80b89e0a6",
  "name": "Paul",
  "photo_id": "p-a2bbb777-67de-4196-a92f-1effba006d13",
  "is_liked": 1,
  "user_id": "u-e6216642-5a01-4e69-913c-b2d19ef9b22e"
}
  • 点赞接口响应:
{
  "UserID": "u-79696bbe-18e5-4e73-bfb7-e8c80b89e0a6",
  "loggedInUserId": "u-43eaf8a2-2f47-4112-88f1-e08a382df59d",
  "likedPhotoId": "p-a2bbb777-67de-4196-a92f-1effba006d13",
  "userLikes": 163,
  "is_liked": 1
}
  • 取消点赞接口响应:
{
    "UserID": "u-79696bbe-18e5-4e73-bfb7-e8c80b89e0a6",
    "loggedInUserId": "u-43eaf8a2-2f47-4112-88f1-e08a382df59d",
    "dislikedPhotoId": "p-a2bbb777-67de-4196-a92f-1effba006d13",
    "userLikes": 162,
    "is_liked": 0
}

当前代码存在的核心问题:

  • 用发布者ID(UserID)作为点赞状态标识,无法区分同一发布者的不同照片
  • 直接修改React状态对象,违反状态不可变原则
  • 本地先修改UI再请求服务端,若请求失败会导致UI与服务端数据不一致
  • 未根据服务端响应更新本地状态
  • 按钮未根据点赞状态动态变化
解决方案

1. 初始化点赞状态

组件加载时,从服务端返回的is_liked字段初始化userLikedPhotos,用photo_id作为键(唯一标识单张照片):

useEffect(() => {
    const headers = {
        "Accept": 'application/json',
        "Authorization": `Bearer ${authToken}`
    };

    axios.get('http://127.0.0.1:8000/api/get-user-uploads-data', {headers})
        .then(resp => {
            setGridData(resp.data);
            // 初始化点赞状态:photo_id为键,is_liked转换为布尔值
            const initialLikes = {};
            resp.data.forEach(photo => {
                initialLikes[photo.photo_id] = photo.is_liked === 1;
            });
            setUserLikedPhotos(initialLikes);
        }).catch(err => {
            console.log(err);
        });
}, [authToken]); // 添加authToken依赖,token变化时重新加载数据

2. 统一点赞/取消点赞逻辑

合并点赞和取消点赞操作,改用函数式更新状态,依赖服务端响应更新UI(避免本地修改后请求失败的不一致):

const handleLikeToggle = (photo) => {
    const isCurrentlyLiked = userLikedPhotos[photo.photo_id];
    const url = isCurrentlyLiked 
        ? 'http://127.0.0.1:8000/api/dislike' 
        : 'http://127.0.0.1:8000/api/like';
    
    const data = isCurrentlyLiked 
        ? { 
            UserID: photo.UserID, 
            userName: photo.name, 
            dislikedPhotoId: photo.photo_id 
        }
        : { 
            UserID: photo.UserID, 
            userName: photo.name, 
            likedPhotoId: photo.photo_id 
        };

    const headers = {
        "Accept": 'application/json',
        "Authorization": `Bearer ${authToken}`
    };

    axios.post(url, data, { headers })
        .then(resp => {
            const respData = resp.data;
            const photoId = isCurrentlyLiked ? respData.dislikedPhotoId : respData.likedPhotoId;
            
            // 更新照片列表数据
            setGridData(prevGrid => prevGrid.map(item => 
                item.photo_id === photoId 
                    ? { ...item, likes: respData.userLikes, is_liked: respData.is_liked }
                    : item
            ));
            
            // 更新点赞状态
            setUserLikedPhotos(prevLikes => ({
                ...prevLikes,
                [photoId]: respData.is_liked === 1
            }));
        }).catch(err => {
            console.error('点赞操作失败:', err);
            // 可选:添加失败提示,比如alert('操作失败,请重试')
        });
};

3. 动态调整按钮显示

根据当前点赞状态改变按钮的文本和样式,给用户清晰的状态反馈:

{
    gridData.map((photo) => {
        const isLiked = userLikedPhotos[photo.photo_id];
        return (
            <div key={photo.photo_id} className="userDetails">
                <span className="likesAmt">❤️ {photo.likes}</span>
                <Button 
                    variant={isLiked ? "danger" : "success"} 
                    onClick={() => handleLikeToggle(photo)}
                >
                    {isLiked ? "取消点赞" : "点赞"}
                </Button>
            </div>
        )
    })
}

完整修改后的代码

const Grid = () => {
    const authToken = localStorage.getItem('token');

    const [gridData, setGridData] = useState([]);
    const [userLikedPhotos, setUserLikedPhotos] = useState({});

    useEffect(() => {
        if (!authToken) return; // 无token时不请求
        
        const headers = {
            "Accept": 'application/json',
            "Authorization": `Bearer ${authToken}`
        };

        axios.get('http://127.0.0.1:8000/api/get-user-uploads-data', {headers})
            .then(resp => {
                setGridData(resp.data);
                // 初始化点赞状态
                const initialLikes = {};
                resp.data.forEach(photo => {
                    initialLikes[photo.photo_id] = photo.is_liked === 1;
                });
                setUserLikedPhotos(initialLikes);
            }).catch(err => {
                console.log(err);
            });
    }, [authToken]);

    const handleLikeToggle = (photo) => {
        const isCurrentlyLiked = userLikedPhotos[photo.photo_id];
        const url = isCurrentlyLiked 
            ? 'http://127.0.0.1:8000/api/dislike' 
            : 'http://127.0.0.1:8000/api/like';
        
        const data = isCurrentlyLiked 
            ? { 
                UserID: photo.UserID, 
                userName: photo.name, 
                dislikedPhotoId: photo.photo_id 
            }
            : { 
                UserID: photo.UserID, 
                userName: photo.name, 
                likedPhotoId: photo.photo_id 
            };

        const headers = {
            "Accept": 'application/json',
            "Authorization": `Bearer ${authToken}`
        };

        axios.post(url, data, { headers })
            .then(resp => {
                const respData = resp.data;
                const photoId = isCurrentlyLiked ? respData.dislikedPhotoId : respData.likedPhotoId;
                
                // 更新照片列表数据
                setGridData(prevGrid => prevGrid.map(item => 
                    item.photo_id === photoId 
                        ? { ...item, likes: respData.userLikes, is_liked: respData.is_liked }
                        : item
                ));
                
                // 更新点赞状态
                setUserLikedPhotos(prevLikes => ({
                    ...prevLikes,
                    [photoId]: respData.is_liked === 1
                }));
            }).catch(err => {
                console.error('点赞操作失败:', err);
            });
    };

    return (
        <section className="gallery">
            <div className="container">
                <div className="img-container">
                    {gridData.map((photo) => {
                        const isLiked = userLikedPhotos[photo.photo_id];
                        return (
                            <div key={photo.photo_id} className="userDetails">
                                <span className="likesAmt">❤️ {photo.likes}</span>
                                <Button 
                                    variant={isLiked ? "danger" : "success"} 
                                    onClick={() => handleLikeToggle(photo)}
                                >
                                    {isLiked ? "取消点赞" : "点赞"}
                                </Button>
                            </div>
                        );
                    })}
                </div>
            </div>
        </section>
    );
};

export default Grid;

关键修改说明

  • 用photo_id作为点赞状态的键,确保同一发布者的不同照片能独立管理点赞状态
  • 采用React函数式更新状态,严格遵循状态不可变原则
  • 服务端请求成功后再更新UI,保证本地状态与服务端数据一致
  • 动态调整按钮的文本和样式,提供直观的状态反馈
  • 添加authToken作为useEffect依赖,token变化时自动重新加载数据

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 23:10:46