EP230. “wp_interactivity_state 跨 Block 共享全局状态”
🔒 登录后可标记已读这一章的收尾:终于用上 state(跟 context 相对的全局共享状态),回答「什么时候该用 state 而不是 context」——答案是:只要需要的数据跨越单个 Block 实例的边界(比如「统计页面上所有 Quiz 实例里,一共答对了几道题」),就该用 state。做法是新建一个独立插件(solved-counter),跟已有的 Quiz Block 共用同一个命名空间(create-block)——只要命名空间一致,不同插件、不同 Block 类型、甚至同一 Block 的多个实例,都能共享同一份 state。state 用 wp_interactivity_state() 这个 PHP 函数初始化,多个地方各自初始化同一个字段时 WordPress 会智能合并,不会报错也不会互相覆盖冲突。
涉及文件
wp-content/plugins/solved-counter/(新建插件)wp-content/plugins/solved-counter/src/render.php(修改)wp-content/plugins/interactivity-quiz/src/render.php(修改,初始化同一个命名空间下的state)wp-content/plugins/interactivity-quiz/src/view.js(修改,读取store()返回值里的state并递增)
代码实现
终端命令:新建第二个插件:
cd 你的WordPress安装目录/wp-content/plugins
npm create @wordpress/create-block@latest solved-counter -- --template @wordpress/create-block/interactive-template
solved-counter/src/render.php(完整文件):
<?php
/**
* PHP file to use when rendering the block type on the server to show on the front end.
* ...
*/
wp_interactivity_state('create-block', array('solvedCount' => 0, 'grassColor' => 'green'));
?>
<div data-wp-interactive="create-block">
<p>Questions solved: <strong><span data-wp-text="state.solvedCount"></span></strong></p>
</div>
interactivity-quiz/src/render.php:顶部同样初始化同一个命名空间的 state(字段可以不完全相同):
<?php
/**
* PHP file to use when rendering the block type on the server to show on the front end.
* ...
*/
wp_interactivity_state('create-block', array('solvedCount' => 0, 'skyColor' => 'blue'));
$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, 'showSorry' => false, 'correctAnswer' => $attributes['correctAnswer']);
?>
interactivity-quiz/src/view.js:从 store() 的返回值里解构出 state,答对时递增:
import { store, getContext } from "@wordpress/interactivity"
const { state } = store("create-block", {
actions: {
guessAttempt: () => {
const context = getContext()
if (!context.solved) {
if (context.index === context.correctAnswer) {
state.solvedCount++
console.log(state)
context.showCongrats = true
setTimeout(() => {
context.solved = true
}, 1000)
} else {
context.showSorry = true
setTimeout(() => {
context.showSorry = false
}, 2600)
}
}
},
toggle: () => {
const context = getContext()
context.isOpen = !context.isOpen
}
},
callbacks: {
noclickclass: () => {
const context = getContext()
return context.solved && context.correct
},
fadedclass: () => {
const context = getContext()
return context.solved && !context.correct
},
logIsOpen: () => {
const { isOpen } = getContext()
console.log(`Is open: ${isOpen}`)
}
}
})
关键改动点:
- 判断该用
state还是context的核心标准:context是「某一个 Block 实例自己的数据」,天然被隔离在这个实例内部;state是「跨越 Block 实例边界」的全局共享数据——这一讲的例子(统计页面上多个 Quiz 实例总共答对了几道题)天然需要跨实例累加,只能用state - 新建一个完全独立的插件(
solved-counter)承载「统计」这个 Block:用跟 EP224 一样的@wordpress/create-block脚手架命令,只是插件名字不同——这个新插件本身不需要知道任何关于 Quiz Block 的具体逻辑,只需要「订阅」同一份共享状态 - 命名空间(namespace)是连接不同 Block/插件共享
state的关键纽带:block.json里name字段格式是命名空间/Block名(比如create-block/interactivity-quiz),斜杠前面的部分就是命名空间——只要两个 Block(哪怕来自完全不同的插件)在各自的 PHP/JS 里都使用同一个命名空间字符串,它们就能读写同一份state wp_interactivity_state($命名空间, $初始值数组)——初始化/合并全局 state 的 PHP 函数:可以在多个不同的地方(这里是两个不同插件各自的render.php)分别调用,只要命名空间相同,WordPress 会把各处传入的数组智能合并成一份——solved-counter初始化了solvedCount/grassColor,interactivity-quiz也初始化了solvedCount/skyColor,两边字段有重叠(solvedCount)也有各自独有的(grassColor/skyColor),合并后的最终state会同时包含这三个字段data-wp-interactive="create-block"——把新 Block 的 HTML「加入」这个命名空间:跟context一样,想读取state里的数据,当前这段 HTML 必须先声明自己属于哪个命名空间,不声明的话即使字段名对得上也读不到值data-wp-text="state.solvedCount"——直接访问state,不需要getContext()那样额外调用一个函数:state在 HTML 属性里可以像context一样直接用点记法访问;但在 JS 里读写state的方式和context不同——不是调用一个getState()函数,而是从store()函数的返回值里解构出来const {state} = store("create-block", {...})——从store()的返回值解构出state变量:之前的写法都是store("create-block", {...})这样直接调用、不接收返回值;这一讲需要读写state,所以要在调用store()时用解构语法接住返回值里的state属性——拿到之后,state.solvedCount++这类操作可以在任何actions/callbacks函数内部直接使用- 多插件共用同一个
state时也要注意「防御性」:如果页面上只插入了「统计 Block」,却一个 Quiz Block 都没有插入,由于「统计 Block」自己的render.php里也初始化了solvedCount的默认值0,不会因为 Quiz Block 缺席而报错或者读到undefined——这体现了「每个用到某个state字段的地方都应该自己负责初始化默认值」这个稳健的设计习惯,不能假设「一定有别的 Block 会先初始化好这个字段」 - 验证「跨实例累加」效果:页面上插入 3 个 Quiz 实例 + 1 个统计 Block,初始都显示 0;答对第一题变成 1,答对第二题变成 2,答对第三题变成 3——证明这个数字确实是跨 3 个独立 Block 实例累加的,不是某一个实例自己的局部计数
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
wp_interactivity_state($命名空间, $初始值数组) | WP 内建 function | 初始化/合并指定命名空间下的全局共享 state,可在多处调用、智能合并 |
data-wp-interactive="命名空间" | Interactivity API HTML 属性 | 让某段 HTML 加入指定命名空间,才能访问该命名空间下的 state/context |
const {state} = store(命名空间, {...}) | JS 用法 | 从 store() 调用的返回值解构出 state,在 actions/callbacks 里直接读写 |
data-wp-text="state.字段名" | Interactivity API HTML 属性 | 跟访问 context 语法一致,直接用点记法读取 state |
常见坑
- 把「需要跨 Block 实例共享」的数据错误地放进
context——每个实例会各自独立,无法实现真正的全局统计 - 不同插件/Block 想共享同一份
state,却使用了不同的命名空间字符串——即使字段名一模一样,也完全访问不到彼此的数据,因为命名空间不匹配 - 忘记给用到
state的 Block 声明data-wp-interactive——即使state已经在别处正确初始化,没有声明所属命名空间的 HTML 依然读不到值 - JS 里想读写
state却继续用getContext()或者干脆没有解构store()的返回值——state需要从store()调用的返回值里解构获得,跟getContext()的调用方式不同 - 只在「主 Block」里初始化某个
state字段的默认值,假设其他用到这个字段的 Block 一定会先加载——应该让每个用到某字段的地方都各自负责初始化默认值,避免因为加载顺序或者选择性插入某些 Block 导致读到未定义的值
[截图:前台页面同时插入 3 个 Quiz 实例和 1 个 Questions solved 统计 Block,依次答对后统计数字从 0 跨实例累加到 3 的效果]
延伸 / 后续讲座会用到
这一讲是 Interactivity API 基础篇的收尾。作者预告了一个尚未正式并入 WordPress 核心的后续功能——Interactivity Router(写这门课时是独立的实验性包,未来会带来类似 Next.js/React Router 那种「点击链接无刷新切换页面、同步浏览器地址栏和前进后退历史」的单页应用体验),等它正式发布会另开新课讲解。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 30, EP230