WP DEVELOP

EP149. “点击判分与 React useState 管理组件状态”

首页 WordPress 开发课程 GUTENBERG BLOCK 入门(block.json) · EP149
约 15 分钟· #EP149#GUTENBERG BLOCK 入门(block.json)
🔒 登录后可标记已读

给前台答案列表接上真正的点击判分逻辑:点击某个答案,对比它的下标是否等于 correctAnswer,正确/错误分别显示带图标的提示语。核心知识点是 React 的 useState——不能像原生 JS/jQuery 那样直接找到 DOM 元素手动加减 class,而是要让组件自己维护一份「状态」(这里是 isCorrectundefined/true/false),状态一变化 React 就会自动重新渲染,JSX 里用状态值驱动要不要显示某个提示。


涉及文件

  • wp-content/plugins/are-you-paying-attention/src/frontend.js (修改)

代码实现

src/frontend.js(完整文件)

import React, {useState} from 'react'
import ReactDOM from 'react-dom'
import "./frontend.scss"

const divsToUpdate = document.querySelectorAll(".paying-attention-update-me")

divsToUpdate.forEach(function(div) {
  const data = JSON.parse(div.querySelector("pre").innerHTML)
  ReactDOM.render(<Quiz {...data} />, div)
  div.classList.remove("paying-attention-update-me")
})

function Quiz(props) {
  const [isCorrect, setIsCorrect] = useState(undefined)

  function handleAnswer(index) {
    if (index == props.correctAnswer) {
      setIsCorrect(true)
    } else {
      setIsCorrect(false)
    }
  }

  return (
    <div className="paying-attention-frontend">
      <p>{props.question}</p>
      <ul>
        {props.answers.map(function(answer, index) {
          return <li onClick={() => handleAnswer(index)}>{answer}</li>
        })}
      </ul>
      <div className={"correct-message" + (isCorrect == true ? " correct-message--visible" : "")}>
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" className="bi bi-emoji-smile" viewBox="0 0 16 16">
          <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
          <path d="M4.285 9.567a.5.5 0 0 1 .683.183A3.498 3.498 0 0 0 8 11.5a3.498 3.498 0 0 0 3.032-1.75.5.5 0 1 1 .866.5A4.498 4.498 0 0 1 8 12.5a4.498 4.498 0 0 1-3.898-2.25.5.5 0 0 1 .183-.683zM7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm4 0c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5z"/>
        </svg>
        <p>That is correct!</p>
      </div>
      <div className={"incorrect-message" + (isCorrect === false ? " correct-message--visible" : "")}>
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" className="bi bi-emoji-frown" viewBox="0 0 16 16">
          <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
          <path d="M4.285 12.433a.5.5 0 0 0 .683-.183A3.498 3.498 0 0 1 8 10.5c1.295 0 2.426.703 3.032 1.75a.5.5 0 0 0 .866-.5A4.498 4.498 0 0 0 8 9.5a4.5 4.5 0 0 0-3.898 2.25.5.5 0 0 0 .183.683zM7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm4 0c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5z"/>
        </svg>
        <p>Sorry, try again.</p>
      </div>
    </div>
  )
}

关键改动点:

  • import React, {useState} from 'react'useState 是 React 提供的 Hook,专门用来给函数组件添加「状态」这个能力
  • const [isCorrect, setIsCorrect] = useState(undefined):这行用了数组解构语法。useState(初始值) 返回一个包含两个元素的数组:第一个是当前状态值(isCorrect),第二个是专门用来更新这个状态的函数(setIsCorrect)——初始值给 undefined,表示「用户还没点过任何答案」
  • React 状态的核心规则:永远不直接读写 DOM,只通过状态驱动界面。点击答案后调用 setIsCorrect(true/false) 更新状态,React 侦测到状态变化后会自动重新执行整个组件函数、根据最新的状态值重新计算该显示什么——不需要自己写「找到某个 div,手动加/删 class」这类命令式代码
  • handleAnswer(index):对比点击的答案下标跟 props.correctAnswer 是否相等,分别调用 setIsCorrect(true)setIsCorrect(false)
  • <li onClick={() => handleAnswer(index)}>:同样要用箭头函数包一层再调用(跟编辑器端的删除按钮同理),否则会在渲染时立即执行而不是等点击时才执行
  • 两个提示 <div>className 用字符串拼接 + 三元运算符动态决定要不要加上 --visible 修饰符 class:isCorrect == true 时给「正确」提示加上可见 class;isCorrect === false 时给「错误」提示加上可见 class——这里特意用 ===(严格相等)而不是 ==,是为了避免 undefined(初始状态)被宽松相等判断误判成 false,导致页面一加载就显示「错误」提示
  • 图标直接从开源图标库(Bootstrap Icons,icons.getbootstrap.com)复制 SVG 代码粘贴进 JSX——原始 SVG 里的 class 属性要手动改成 JSX 认的 classNamefill="currentColor" 这类属性按需删除保留原始配色
  • CSS 负责淡入淡出的过渡动画和自动消失的计时(转录提到大约 2.5 秒后消失),这部分是纯 CSS 动画,跟 React/JS 逻辑无关,只由 class 的增删触发
  • 目前有个已知的小限制:一旦 isCorrect 被设置成 false(答错一次),如果再点别的错误答案,状态值仍然是 false,没有变化,React 不会重新触发渲染——所以连续点错时不会重复看到「错误」提示的淡入动画,这个细节问题在后续讲座里处理

[截图:前台页面点击某个答案后,出现的"That is correct!"或"Sorry, try again."提示消息(含笑脸/哭脸 SVG 图标)]


Hook / Function 速查

名称类型用途
useState(initialValue)React Hook给函数组件添加状态,返回 [当前值, 更新函数] 数组
数组解构 const [a, b] = arrJS 语法按位置把数组元素分别赋值给多个变量

常见坑

  • 试图直接用 document.querySelector() 找到 DOM 元素手动加减 class 来实现「显示/隐藏提示」——这是 React 组件里应该避免的命令式操作,正确做法是用状态驱动渲染结果,让 React 自己决定最终输出什么 HTML
  • == 判断「是否等于 false」——undefined == false 在 JS 里的宽松相等判断下不成立,但如果检查逻辑写得不够严谨容易让初始状态被误判,用 === 严格相等更安全
  • onClick 直接写函数调用 onClick={handleAnswer(index)}——会在组件渲染阶段立即执行,而不是等待用户真正点击

延伸 / 后续讲座会用到

下一讲会打磨一些交互细节(比如允许连续修改答案、样式收尾等),把前台体验做得更完整。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 25, EP149