如何用JavaScript在类Twitter Web应用中异步切换点赞/取消点赞按钮
问题:CS50W类Twitter应用点赞按钮异步切换失效
我正在修读哈佛大学CS50W在线课程,开发类Twitter Web应用。需求是根据用户是否点赞过帖子,显示「点赞」或「取消点赞」按钮,同时异步更新点赞计数器。目前已实现点赞数的无刷新更新,但点击按钮后必须刷新页面才能切换按钮状态,无法异步切换。
相关代码
models.py
class Post(models.Model): """ Model representing a post. """ user = models.ForeignKey(User, on_delete=models.CASCADE) content = models.TextField() timestamp = models.DateTimeField(auto_now_add=True) no_of_likes = models.IntegerField(default=0) def __str__(self): return f"Post {self.id} by {self.user.username} on {self.timestamp}" class Like(models.Model): """ Model representing a like. """ user = models.ForeignKey(User, on_delete=models.CASCADE) post = models.ForeignKey(Post, on_delete=models.CASCADE) timestamp = models.DateTimeField(auto_now_add=True) def __str__(self): return f"{self.user} likes {self.post}"
urls.py
path("", views.index, name="index"), path("like/<int:post_id>", views.like, name="like"), path("unlike/<int:post_id>", views.unlike, name="unlike"),
views.py
def index(request): """ Home page. """ posts = Post.objects.all().order_by('-timestamp') paginator = Paginator(posts, 5) page_number = request.GET.get('page') page_obj = paginator.get_page(page_number) likes = Like.objects.all() # Make a list of liked posts. liked_posts = [] try: for like in likes: if like.user.id == request.user.id: liked_posts.append(like.post.id) except: liked_posts = [] return render(request, "network/index.html", { "posts": posts, "page_obj": page_obj, "likes": likes, "liked_posts": liked_posts, }) @login_required def like(request, post_id): post = Post.objects.get(pk=post_id) user = User.objects.get(pk=request.user.id) like = Like.objects.create(user=user, post=post) like.save() # Update no of likes. post.no_of_likes = Like.objects.filter(post=post).count() post.save() return JsonResponse({"message": "successfully liked", "no_of_likes": post.no_of_likes}) @login_required def unlike(request, post_id): post = Post.objects.get(pk=post_id) user = User.objects.get(pk=request.user.id) like = Like.objects.filter(user=user, post=post) like.delete() # Update no of likes. post.no_of_likes = Like.objects.filter(post=post).count() post.save() return JsonResponse({"message": "successfully unliked", "no_of_likes": post.no_of_likes})
index.html(含JavaScript)
{% if post.id not in liked_posts %} <button type="button" class="btn btn-primary" id="like{{ post.id }}" onclick="like('{{ post.id }}')">Like</button> {% else %} <button type="button" class="btn btn-primary" id="unlike{{ post.id }}" onclick="unlike('{{ post.id }}')">Unlike</button> {% endif %} <script> function like(id) { fetch(`/like/${id}`, { method: "POST", headers: { "Content-Type": "application/json", "X-CSRFToken": "{{ csrf_token }}", }, }) .then(response => response.json()) .then(data => { document.querySelector(".no-of-likes").innerHTML = data.no_of_likes + " likes"; }) .then(() => { document.getElementById("unlike" + id).style.display = "block"; document.getElementById("like" + id).style.display = "none"; }); } function unlike(id) { fetch(`/unlike/${id}`, { method: "POST", headers: { "Content-Type": "application/json", "X-CSRFToken": "{{ csrf_token }}", }, }) .then(response => response.json()) .then(data => { document.querySelector(".no-of-likes").innerHTML = data.no_of_likes + " likes"; }) .then(() => { document.getElementById("unlike" + id).style.display = "none"; document.getElementById("like" + id).style.display = "block"; }); } </script>
问题表现
- 点击点赞按钮后,点赞数更新,但必须刷新页面才能切换为取消点赞按钮;
- 点击取消点赞按钮后,点赞数更新,但必须刷新页面才能切换为点赞按钮。
解决方案
核心问题
页面渲染时只会生成「点赞」或「取消点赞」中的一个按钮,另一个按钮不存在于DOM中,导致JS无法通过display属性切换状态。
修复步骤
1. 修改HTML模板,同时渲染两个按钮
把原有的if-else逻辑改为同时渲染两个按钮,通过初始CSS状态控制显示:
<!-- 同时渲染两个按钮,根据初始点赞状态控制显示 --> <button type="button" class="btn btn-primary" id="like{{ post.id }}" onclick="like('{{ post.id }}')" {% if post.id in liked_posts %}style="display:none;"{% endif %}> Like </button> <button type="button" class="btn btn-primary" id="unlike{{ post.id }}" onclick="unlike('{{ post.id }}')" {% if post.id not in liked_posts %}style="display:none;"{% endif %}> Unlike </button> <!-- 给每个帖子的点赞计数器添加唯一ID,避免全局选择错误 --> <div class="no-of-likes" id="likes-count-{{ post.id }}">{{ post.no_of_likes }} likes</div>
2. 修正JavaScript中的选择器问题
原代码中document.querySelector(".no-of-likes")会选中页面第一个匹配元素,导致所有帖子点赞数同步错误,需改为针对当前帖子的计数器:
function like(id) { fetch(`/like/${id}`, { method: "POST", headers: { "Content-Type": "application/json", "X-CSRFToken": "{{ csrf_token }}", }, }) .then(response => response.json()) .then(data => { // 更新当前帖子的点赞数 document.getElementById(`likes-count-${id}`).innerHTML = `${data.no_of_likes} likes`; // 切换按钮显示状态 document.getElementById(`like${id}`).style.display = "none"; document.getElementById(`unlike${id}`).style.display = "block"; }) .catch(error => console.error('点赞失败:', error)); } function unlike(id) { fetch(`/unlike/${id}`, { method: "POST", headers: { "Content-Type": "application/json", "X-CSRFToken": "{{ csrf_token }}", }, }) .then(response => response.json()) .then(data => { // 更新当前帖子的点赞数 document.getElementById(`likes-count-${id}`).innerHTML = `${data.no_of_likes} likes`; // 切换按钮显示状态 document.getElementById(`unlike${id}`).style.display = "none"; document.getElementById(`like${id}`).style.display = "block"; }) .catch(error => console.error('取消点赞失败:', error)); }
3. 优化views.py中的点赞列表生成逻辑(可选)
原代码遍历所有Like对象效率低下,改为直接过滤当前用户的点赞记录:
def index(request): """ Home page. """ posts = Post.objects.all().order_by('-timestamp') paginator = Paginator(posts, 5) page_number = request.GET.get('page') page_obj = paginator.get_page(page_number) # 直接获取当前用户点赞的帖子ID列表 liked_posts = [] if request.user.is_authenticated: liked_posts = Like.objects.filter(user=request.user).values_list('post__id', flat=True) return render(request, "network/index.html", { "page_obj": page_obj, "liked_posts": liked_posts, })
额外优化建议
- 移除Post模型中的
no_of_likes字段,通过post.like_set.count()实时获取点赞数,避免数据不一致; - 给Like模型添加唯一约束,防止用户重复点赞:
class Like(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) post = models.ForeignKey(Post, on_delete=models.CASCADE) timestamp = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('user', 'post') # 确保同一用户不能重复点赞同一帖子 def __str__(self): return f"{self.user} likes {self.post}"
内容的提问来源于stack exchange,提问作者Alfalfa9055
相关产品推荐
相关产品推荐

