WP DEVELOP

EP227. “PHP 整理 context 数据结构与 context 逐级合并机制”

首页 WordPress 开发课程 INTERACTIVITY API · EP227
约 16 分钟· #EP227#INTERACTIVITY API
🔒 登录后可标记已读

这一讲是这一章最关键的转折点,回答两个核心问题:① 什么时候该用 data-wp-each 循环,什么时候该用传统 PHP foreach + Interactivity API 混合——结论是:只有当列表数据真的需要在客户端「实时增删改」(比如待办事项)时才用 data-wp-each;像 Quiz 的答案列表这种「一次性从服务器渲染出来,之后不会再动态增删」的场景,混用「PHP 循环生成 HTML + 逐项挂 data-wp-context」反而更简单、没有 CSS class 相关的已知 bug。② Interactivity API 的 context 是按 DOM 层级逐级合并的——子元素能同时访问自己声明的 context 和所有祖先元素的 context,这个特性让「判断点击的答案是否正确」这类需要跨层级比较数据的逻辑变得极其简单。这一讲同时用 PHP 提前把「每个答案的下标/文字/是否正确」整理成一份干净的 answers 数组,塞进精心设计的 $ourContext,替代掉之前直接把整个 $attributes 塞进 context 的粗暴做法。


涉及文件

  • wp-content/plugins/interactive-quiz/src/render.php (修改)
  • wp-content/plugins/interactive-quiz/src/view.js (修改)

代码实现

src/render.php(完整文件)

<?php
/**
 * PHP file to use when rendering the block type on the server to show on the front end.
 * ...
 */

	$answers = array();
	for ($i = 0; $i < count($attributes['answers']); $i++) {
		$answers[$i]['index'] = $i;
		$answers[$i]['text'] = $attributes['answers'][$i];
		$answers[$i]['correct'] = $attributes['correctAnswer'] == $i;
	}
	$ourContext = array('answers' => $answers, 'solved' => false, 'showCongrats' => false, 'showSorrry' => false, 'correctAnswer' => $attributes['correctAnswer']);

?>


<div style="background-color: <?php echo $attributes['bgColor'] ?>" class="paying-attention-frontend" data-wp-interactive="create-block" <?php echo wp_interactivity_data_wp_context($ourContext) ?>>
	<p><?php echo $attributes['question'] ?></p>
	<ul>
		<?php
			foreach($ourContext['answers'] as $answer) { ?>
				<li <?php echo wp_interactivity_data_wp_context($answer) ?> data-wp-on--click="actions.guessAttempt"><?php echo $answer['text'] ?></li>
			<?php }
		?>
	</ul>
</div>

src/view.js:验证「context 逐级合并」效果,比较点击的答案下标与正确答案下标

import { store, getContext } from "@wordpress/interactivity"

store("create-block", {
  actions: {
    guessAttempt: () => {
      const context = getContext()
      console.log(context.index === context.correctAnswer)
    },
    toggle: () => {
      const context = getContext()
      context.isOpen = !context.isOpen
    }
  },
  callbacks: {
    logIsOpen: () => {
      const { isOpen } = getContext()
      // Log the value of `isOpen` each time it changes.
      console.log(`Is open: ${isOpen}`)
    }
  }
})

关键改动点:

  • data-wp-each(上一讲用的方式)的局限:作者提到截至 2024 年 4 月,data-wp-each 循环体内的直接子元素(这里是 <li>)在管理 CSS class 时(比如想动态加/删某个 class)会遇到一些奇怪的 bug,需要额外多包一层 <span>/<div> 才能绕过——这大概率是当时 Interactivity API 还比较新遇到的实现细节问题;更关键的是,答案列表本身不需要支持「实时增删改」这种动态行为(题目定好之后,答案数量和内容不会在用户浏览时被修改),所以完全没必要为了用一个「支持动态列表」的工具而承受它的已知副作用
  • 判断标准:数据会不会在客户端被动态增删改——如果会(比如一个可以让用户添加/删除项目的待办列表),data-wp-each + context/state 驱动是正确工具;如果不会(内容一旦从服务器渲染出来就不再变化,只是需要绑定点击事件做判断),改用「PHP 循环生成静态 HTML + 每一项各自挂一份 data-wp-context」更简单可靠
  • $ourContext 是精心设计的「只包含真正需要的数据」的整理结果,不是直接把整个 $attributes 塞进去(那样会带上完全用不到的 bgColor 等字段,虽然不算错但不够干净):
    • answers:一个新数组,每一项包含 index(下标)、text(文字)、correct(是否是正确答案,true/false
    • solved/showCongrats/showSorrry(注意这里是原始代码里的拼写,多了一个 r):预留的几个状态标记,初始都是 false,后续讲座会用来控制显示「恭喜/抱歉」提示
    • correctAnswer:直接从 $attributes 里带过来的正确答案下标
  • 用经典 PHP for 循环手工构建 $answers 数组:作者坦言这里本可以用更「函数式」的写法(类似 JS 的 .map()),但自认 PHP 不如 JS 熟练,用最朴素的 for 循环 + 逐个赋值也完全能达到同样效果——这是一个坦诚展示「不追求语言炫技,能解决问题就行」的实际例子
  • $answers[$i]['correct'] = $attributes['correctAnswer'] == $i:提前在 PHP 端就算好每个答案「是不是正确答案」这个布尔值——虽然这一讲最后揭示这一步其实是多余的(详见下方),但作者选择保留这个「代码里确实存在但没有被真正用上」的思路过程,坦诚地说明「这是我当时没想到更优雅方案的产物,不代表这是最佳实践」
  • wp_interactivity_data_wp_context() 可以在同一个模板里反复调用,分别给不同层级的元素挂不同的 context:外层 <div>$ourContext(整体数据),内层每个 <li> 再各自挂一份 $answer(当前这一项的数据)——两次调用互不冲突
  • context 按 DOM 树逐级合并的核心机制:子元素/孙元素能同时访问「自己声明的 context」和「祖先元素声明的所有 context」合并后的结果——点击某个 <li> 时,getContext() 返回的对象里,既有这个 <li> 自己声明的 index/text/correct,也有外层 <div> 声明的 correctAnswer/solved/answers 等——不需要手动逐层往下传递数据,WordPress 自动把「从根到当前节点」路径上所有声明过的 context 合并成一份
  • 验证合并效果的实验:给某个 <li> 临时加一个 data-wp-context='{"skyColor": "blue"}',点击它时 getContext() 打印出来的对象里能同时看到 skyColor: "blue"(自己声明的)和 correctAnswer(从外层 <div> 继承来的)——证明合并是双向兼容的,不是「子元素覆盖父元素」或者「只能访问最近一层」
  • 真正的判分逻辑因为有了这套合并机制而变得极其简单context.index === context.correctAnswer——index 来自这个 <li> 自己的 context(当前点击的是第几个答案),correctAnswer 来自外层 <div> 继承来的 context(正确答案的下标),两者可以直接比较,不需要手动查找/传递跨层级的数据
  • answer['correct'] 这个字段目前是「计算了但没真正用上」的冗余数据:作者反思说,其实完全不需要在 PHP 端提前算好「这个答案是否正确」这个布尔值,因为 JS 端已经能通过 context.index === context.correctAnswer 直接比较下标算出同样的结果——这是刻意留下的一个「不完美但真实」的过程,提醒读者「不用纠结于第一次没想到最简方案,先跑通、再反思优化是正常的开发节奏」

Hook / Function 速查

名称类型用途
wp_interactivity_data_wp_context($数组)(可重复调用)WP 内建 function给 DOM 树的不同层级各自挂一份局部 context,WordPress 自动逐级合并
getContext() 返回值Interactivity API 机制包含「当前元素自己声明的 context」+「所有祖先元素声明的 context」合并后的结果

常见坑

  • 给「不需要客户端实时增删改」的静态列表也套用 data-wp-each + <template>——徒增复杂度,还可能撞上当时版本已知的 CSS class 管理 bug
  • 误以为 context 只能访问「最近一层」声明的数据,看不到更上层祖先的 context——实际上是整条路径逐级合并,子元素能同时访问自己和所有祖先的数据
  • 为了「判断是否正确」而在 PHP 端提前算好一个 correct 布尔值存进 context,却没意识到 JS 端本来就能通过比较 indexcorrectAnswer 两个已有字段直接算出同样的结果——增加了不必要的冗余数据
  • 循环变量重新赋值时踩到 PHP「数组元素是字符串还是数组」的类型混淆——$answers 数组循环前是普通字符串数组,处理后每一项变成关联数组,输出时忘记多一层 ['text'] 会报「数组转字符串」的警告

延伸 / 后续讲座会用到

下一讲要真正把「点击后显示恭喜/抱歉提示、答案旁边显示对错图标」这个视觉反馈做出来。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 30, EP227