EP227. “PHP 整理 context 数据结构与 context 逐级合并机制”
🔒 登录后可标记已读这一讲是这一章最关键的转折点,回答两个核心问题:① 什么时候该用 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 端本来就能通过比较index和correctAnswer两个已有字段直接算出同样的结果——增加了不必要的冗余数据 - 循环变量重新赋值时踩到 PHP「数组元素是字符串还是数组」的类型混淆——
$answers数组循环前是普通字符串数组,处理后每一项变成关联数组,输出时忘记多一层['text']会报「数组转字符串」的警告
延伸 / 后续讲座会用到
下一讲要真正把「点击后显示恭喜/抱歉提示、答案旁边显示对错图标」这个视觉反馈做出来。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 30, EP227