WP DEVELOP

EP109. “点赞实时更新与删除赞的服务端逻辑”

首页 WordPress 开发课程 LIKE / HEART 功能 · EP109
约 15 分钟· #EP109#LIKE / HEART 功能
🔒 登录后可标记已读

📌 并入 EP108 的提醒:下一步给 .like-boxdata-like 属性时,如果当前用户还没点过赞,$existQuery->posts[0]->ID 这个写法会因为数组里没有第 0 项而报 PHP 警告(跟很早以前 pageBanner() 函数遇到的「访问不存在的数组项」是同一类问题)。这一讲先照原样写,PHP 警告会先出现,官方会在后面一讲提醒用 isset() 包一层解决,这篇笔记先如实记录当前阶段的代码状态。

这一讲把点赞功能的最后两块拼图补完:① 点赞/取消点赞成功后,心形图标和数字实时更新(不用手动刷新页面);② 实现 deleteLike() 的真正业务逻辑——同样要做权限校验:只有「点赞记录的创建者本人」才能删除这条记录,且要确认目标 ID 真的是一条 like 记录。


涉及文件

  • wp-content/themes/fictional-university-theme/src/modules/Like.js (修改)
  • wp-content/themes/fictional-university-theme/single-professor.php (修改)
  • wp-content/themes/fictional-university-theme/inc/like-route.php (修改)

代码实现

single-professor.php:给 .like-box 新增 data-like 属性,记录当前用户对这位教授的点赞记录 ID(这里的写法在还没点赞时会触发 PHP 警告,属于本讲遗留、下一讲才修的已知问题):

<span class="like-box" data-like="<?php echo $existQuery->posts[0]->ID; ?>" data-professor="<?php the_ID(); ?>" data-exists="<?php echo $existStatus; ?>">

src/modules/Like.jsourClickDispatcher 改用 .attr() 而不是 .data() 读取实时属性值(jQuery 的 .data() 只在页面加载时缓存一次,之后属性值再怎么变都不会反映到 .data() 的返回结果里,必须用 .attr() 才能拿到最新值):

ourClickDispatcher(e) {
  var currentLikeBox = $(e.target).closest(".like-box")

  if (currentLikeBox.attr("data-exists") == "yes") { // 修改:.data() → .attr()
    this.deleteLike(currentLikeBox)
  } else {
    this.createLike(currentLikeBox)
  }
}

createLike() 成功后实时更新界面

createLike(currentLikeBox) {
  $.ajax({
    beforeSend: xhr => {
      xhr.setRequestHeader("X-WP-Nonce", universityData.nonce)
    },
    url: universityData.root_url + "/wp-json/university/v1/manageLike",
    type: "POST",
    data: { "professorId": currentLikeBox.data("professor") },
    success: response => {
      currentLikeBox.attr("data-exists", "yes")
      var likeCount = parseInt(currentLikeBox.find(".like-count").html(), 10)
      likeCount++
      currentLikeBox.find(".like-count").html(likeCount)
      currentLikeBox.attr("data-like", response) // response 是刚创建的 like 文章 ID
      console.log(response)
    },
    error: response => {
      console.log(response)
    }
  })
}

deleteLike():请求带上要删除的 like 记录 ID + nonce,成功后做相反方向的界面更新

deleteLike(currentLikeBox) {
  $.ajax({
    beforeSend: xhr => {
      xhr.setRequestHeader("X-WP-Nonce", universityData.nonce)
    },
    url: universityData.root_url + "/wp-json/university/v1/manageLike",
    data: { "like": currentLikeBox.attr("data-like") },
    type: "DELETE",
    success: response => {
      currentLikeBox.attr("data-exists", "no")
      var likeCount = parseInt(currentLikeBox.find(".like-count").html(), 10)
      likeCount--
      currentLikeBox.find(".like-count").html(likeCount)
      currentLikeBox.attr("data-like", "")
      console.log(response)
    },
    error: response => {
      console.log(response)
    }
  })
}

inc/like-route.phpdeleteLike() 实现真正的删除逻辑,含权限校验

// wp-content/themes/fictional-university-theme/inc/like-route.php

function deleteLike($data) {
  $likeId = sanitize_text_field($data['like']);
  if (get_current_user_id() == get_post_field('post_author', $likeId) AND get_post_type($likeId) == 'like') {
    wp_delete_post($likeId, true);
    return 'Congrats, like deleted.';
  } else {
    die("You do not have permission to delete that.");
  }
}

关键改动点:

  • parseInt(字符串, 10):从页面读出的赞数原本是字符串(HTML 文本),要先转成数字才能做加减运算,第二个参数 10 指定十进制
  • createLike() 成功后,用 REST 端点返回的新文章 ID(也就是 wp_insert_post() 的返回值)更新 data-like 属性——这样即使用户不刷新页面、连续点赞/取消,JS 也始终知道「当前这条点赞记录的 ID 是多少」,可以在下次点击时正确删除
  • deleteLike($likeId, true) 的第二个参数 true 表示跳过回收站、直接彻底删除——因为点赞记录本身没有「恢复」的意义,不需要走回收站流程
  • deleteLike() 的权限校验用了两个条件:get_current_user_id() == get_post_field('post_author', $likeId)(只有点赞记录的创建者才能删自己的记录,防止恶意用户删除别人的点赞)+ get_post_type($likeId) == 'like'(确认这个 ID 对应的确实是一条 like 记录,不是被人拿来传了博客文章或其他文章的 ID 妄图删除)
  • .data() vs .attr() 的关键差异:jQuery 的 .data() 方法只在第一次访问时读取一次 data-* 属性并缓存,之后即使用 JS 改了属性的实际值,.data() 还是会返回最初缓存的旧值;.attr() 每次调用都会读取元素当前的真实属性值——这个功能里赞的状态会不断被 JS 动态改写,所以必须全部换成 .attr() 才能让「连续点击切换点赞/取消赞」正常工作

[截图:前台点击心形图标后,图标从空心变实心、赞数实时 +1 的效果(无需刷新页面)]


Hook / Function 速查

名称类型用途
parseInt(str, radix)JS 内建函数把字符串解析成整数,radix 指定进制(人类习惯用 10)
get_post_field($field, $post_id)WP 内建 function获取指定文章的某个原生字段值(如 post_author),不需要先建 WP_Query
wp_delete_post($post_id, $force_delete)WP 内建 function删除文章,$force_deletetrue 时跳过回收站直接永久删除
.attr(name)jQuery 方法读取元素当前的真实 HTML 属性值(每次都重新读取,不缓存)
.data(name)jQuery 方法读取 data-* 属性,但只在首次访问时读取并缓存,之后不会感知属性值的变化

常见坑

  • .data() 读取会被 JS 动态修改的属性——.data() 的缓存机制会导致读到的永远是页面加载时的旧值,凡是「状态会被 JS 自己改写、且需要马上读到最新值」的场景都应该用 .attr()
  • deleteLike() 只校验「是不是 like 类型」不校验「是不是自己创建的」——会让任何登录用户都能删除别人的点赞记录,两个条件必须同时满足
  • 数字加减前忘记 parseInt()——JavaScript 里字符串跟数字做 ++/-- 运算不会报错,但结果往往不是预期的数字运算(容易出现字符串拼接式的诡异结果),一定要先转换类型

延伸 / 后续讲座会用到

下一讲会先给一份「jQuery-free 版 Like.js」补充选修内容(同时会正式落实 EP108/EP110 提到的 isset() 修复);再往后课程会转向新的主题:网站正式上线部署相关的准备工作。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 20, EP109(含 EP108 提醒并入)