WP DEVELOP

EP230. “wp_interactivity_state 跨 Block 共享全局状态”

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

这一章的收尾:终于用上 state(跟 context 相对的全局共享状态),回答「什么时候该用 state 而不是 context」——答案是:只要需要的数据跨越单个 Block 实例的边界(比如「统计页面上所有 Quiz 实例里,一共答对了几道题」),就该用 state。做法是新建一个独立插件(solved-counter),跟已有的 Quiz Block 共用同一个命名空间create-block)——只要命名空间一致,不同插件、不同 Block 类型、甚至同一 Block 的多个实例,都能共享同一份 statestatewp_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.jsonname 字段格式是 命名空间/Block名(比如 create-block/interactivity-quiz),斜杠前面的部分就是命名空间——只要两个 Block(哪怕来自完全不同的插件)在各自的 PHP/JS 里都使用同一个命名空间字符串,它们就能读写同一份 state
  • wp_interactivity_state($命名空间, $初始值数组)——初始化/合并全局 state 的 PHP 函数:可以在多个不同的地方(这里是两个不同插件各自的 render.php)分别调用,只要命名空间相同,WordPress 会把各处传入的数组智能合并成一份——solved-counter 初始化了 solvedCount/grassColorinteractivity-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