如何实现帖子点赞/取消点赞后无需刷新页面更新点赞数?
问题描述
开发带帖子点赞功能的项目,前后端点赞/取消点赞交互正常,但点赞数需刷新页面才会更新。尝试将post.likedBy数组长度存入useState,但该值常为undefined,即便显示也无法正常工作。
代码片段
后端代码
async likePost(req, res) { try { const post = await Post.findOne({ where: { id: req.params.id } }); const alreadyLiked = await Likes.findOne({ where: { post_id: post.id, user_id: req.session.user.id } }); if (!alreadyLiked) { const like = await Likes.create({ post_id: post.id, user_id: req.session.user.id }); console.log('post liked'); res.json({ like }); } else { await Likes.destroy({ where: { post_id: post.id, user_id: req.session.user.id } }); console.log('post unliked'); res.sendStatus(200); } } catch (error) { console.log(error); res.sendStatus(500).send('Server Error'); } }
likeSlice代码
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit" import axios from "axios"; const initialState = { likes: [] } export const upvotePost = createAsyncThunk( 'likes/upvotePost', async (id, { rejectWithValue, dispatch }) => { try { const response = await axios.post(`/posts/${id}/likes`); dispatch(addLike(response.data.like)) } catch (error) { rejectWithValue(error.message); } } ) export const likeSlice = createSlice({ name: 'likes', initialState, reducers: { addLike: (state, action) => { state.likes.push(action.payload); } } }) const { addLike } = likeSlice.actions; export default likeSlice.reducer;
帖子组件代码
const OnePost = () => { const post = useSelector(state => state.post.post_id); const user = useSelector(state => state.auth.user); console.log('post', post); const { id } = useParams(); const dispatch = useDispatch(); const num = post.likedBy?.length console.log('num', num); const [like, setLike] = useState(num) console.log('like--->', like); useEffect(() => { dispatch(getOnePost(id)) }, [like]) const navigate = useNavigate(); const openUserPageHandler = () => { dispatch(getOneUser(post.user_id)) } const likePostHandler = () => { // if (post.likedBy?.map(el => el.Likes.user_id == user.id)) { // setLike(prev => prev - 1) dispatch(upvotePost(id)) // } else { // setLike(prev => prev + 1) // dispatch(upvotePost(id)) // } } const avatar = `http://localhost:3001${post.userAvatar}` const createdAt = new Date(post.createdAt).toLocaleString(undefined, { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }); const comments = useSelector(state => state.comment.comments); useEffect(() => { dispatch(getPostComments(id)); }, []) return ( <> <div className='one-post__container'> <div className='one-post'> <div className='one-post__info'> <div className='one-post__info__head'> <div onClick={() => navigate(-1)}> <ArrowBackIcon className='one-post__back__arrow' /> </div> <span className='profile__name'> <h3>Tweet</h3> </span> </div> <div className='one-post__info__mid'> <Link to={`/user/${post.userId}`} className='one-post__link'> <div className='one-post__name' onClick={openUserPageHandler}> <img src={post.userAvatar ? avatar : default_avatar} alt='' className='one-post__profile__avatar' /> <h4 className='one-post__user__name'>{post.userName}</h4> </div> </Link> <div className='one-post__options'> <MoreHoriz /> </div> </div> <div className='one-post__body'>{post.text}</div> <div className='one-post__footer'> <div className='one-post__time'>{createdAt}</div> <div className='one-post__icons'> <div className='one-post__icons__options'> <ChatBubbleOutline fontSize='small' /> </div> <div className='one-post__icons__options'> <FavoriteBorderOutlined fontSize='small' onClick={likePostHandler} /> {like > 0 ? like : null} </div> </div> </div> </div> </div> </div> </> ) }
解决方案
1. 后端返回明确的操作结果和点赞数
后端当前取消点赞仅返回200状态码,前端无法判断操作类型,需修改接口返回操作类型和更新后的点赞数:
async likePost(req, res) { try { const post = await Post.findOne({ where: { id: req.params.id } }); const alreadyLiked = await Likes.findOne({ where: { post_id: post.id, user_id: req.session.user.id } }); let action; if (!alreadyLiked) { await Likes.create({ post_id: post.id, user_id: req.session.user.id }); action = 'like'; } else { await Likes.destroy({ where: { post_id: post.id, user_id: req.session.user.id } }); action = 'unlike'; } // 获取更新后的点赞数 const updatedPost = await Post.findOne({ where: { id: req.params.id }, include: [{ model: Likes }] }); const likeCount = updatedPost.Likes.length; res.json({ success: true, action, likeCount }); } catch (error) { console.log(error); res.status(500).send('Server Error'); } }
2. 重构Redux的likeSlice,关联具体帖子
原Slice仅维护全局likes数组,无法对应单个帖子,改为按帖子ID存储点赞状态:
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit" import axios from "axios"; const initialState = { postLikes: {} // 键为postId,值为{ count: 点赞数, isLiked: 是否已点赞 } } export const upvotePost = createAsyncThunk( 'likes/upvotePost', async (id, { rejectWithValue }) => { try { const response = await axios.post(`/posts/${id}/likes`); return { postId: id, ...response.data }; } catch (error) { return rejectWithValue(error.message); } } ) export const likeSlice = createSlice({ name: 'likes', initialState, extraReducers: (builder) => { builder .addCase(upvotePost.fulfilled, (state, action) => { const { postId, action: likeAction, likeCount } = action.payload; state.postLikes[postId] = { count: likeCount, isLiked: likeAction === 'like' }; }) .addCase(upvotePost.rejected, (state, action) => { console.error('点赞操作失败:', action.payload); }); } }) export default likeSlice.reducer;
3. 组件移除本地useState,直接从Redux/Post状态取数
原组件用useState存储点赞数会导致数据不同步,且useEffect依赖like引发无限循环,修改如下:
const OnePost = () => { const post = useSelector(state => state.post.post_id); const user = useSelector(state => state.auth.user); const postLikeInfo = useSelector(state => state.likes.postLikes[id]); const { id } = useParams(); const dispatch = useDispatch(); const navigate = useNavigate(); // 优先用Redux实时数据,无数据则用post的likedBy const likeCount = postLikeInfo?.count ?? post?.likedBy?.length ?? 0; const isLiked = postLikeInfo?.isLiked ?? post?.likedBy?.some(item => item.user_id === user?.id); // 初始化加载帖子,依赖id而非like,避免无限循环 useEffect(() => { dispatch(getOnePost(id)); }, [id, dispatch]); const likePostHandler = () => { dispatch(upvotePost(id)); }; const avatar = `http://localhost:3001${post.userAvatar}`; const createdAt = new Date(post.createdAt).toLocaleString(undefined, { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }); const comments = useSelector(state => state.comment.comments); useEffect(() => { dispatch(getPostComments(id)); }, [id, dispatch]); return ( <> <div className='one-post__container'> <div className='one-post'> {/* 其余代码不变 */} <div className='one-post__footer'> <div className='one-post__time'>{createdAt}</div> <div className='one-post__icons'> {/* 其余图标不变 */} <div className='one-post__icons__options'> {/* 根据isLiked切换图标 */} {isLiked ? ( <Favorite fontSize='small' onClick={likePostHandler} /> ) : ( <FavoriteBorderOutlined fontSize='small' onClick={likePostHandler} /> )} {likeCount > 0 ? likeCount : null} </div> </div> </div> </div> </div> </> ) }
内容的提问来源于stack exchange,提问作者t1saif
相关产品推荐
相关产品推荐

