如何实现类似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
相关产品推荐
相关产品推荐

