WP DEVELOP

EP138-139. “Deprecated 版本兼容与动态 Block(用 PHP 渲染)”

首页 WordPress 开发课程 插件开发 CH2:插件里的 JS · EP138-139
约 15 分钟· #EP138-139#插件开发 CH2:插件里的 JS
🔒 登录后可标记已读

📌 这两讲是同一节课被截成两个文件(EP138 结尾正好卡在句子中间,EP139 开头逐字接续),内容上是一体的,合并成一篇笔记记录。

先解释「为什么改了 save 函数的 HTML 结构,页面里已插入的旧 Block 会报错」——因为 WordPress 要拿保存过的 HTML 跟当前 save 函数重新生成的结果做比对,对不上就认为数据可能已经不可信;解法是用 deprecated 属性保留一份「旧版本 save/attributes」的存档,让 WordPress 知道这是一次已知的合法变更。但作者随即指出这整套「把内容存成静态字符串」的官方标准做法有个大问题:改了 save 函数不会让网站上已经发布的旧文章自动更新,除非一篇篇手动重新打开保存。所以引出了作者真正偏好的做法——动态 Block(Dynamic Block):JS 的 save 直接返回 null(什么都不存),真正的输出交给 PHP 的 render_callback 函数在每次页面加载时实时生成,这样以后要改样式/逻辑,哪怕已经用在上万篇文章里,改一次 PHP 全部立刻生效


涉及文件

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

代码实现

(过渡阶段,最终被放弃)deprecated 属性的用法示例

// 顶层配置对象新增:
deprecated: [
  {
    attributes: { /* 旧版本的 attributes 定义,从当前版本复制一份存档 */ },
    save: function (props) {
      // 旧版本的 save 函数原样保留在这里
    }
  }
  // 如果之后又要再改一次结构,就在数组最前面再插入新的一个存档对象
]

最终采用的方案——动态 Block:src/index.jssave 直接返回 null

wp.blocks.registerBlockType("ourplugin/are-you-paying-attention", {
  title: "Are You Paying Attention?",
  icon: "smiley",
  category: "common",
  attributes: {
    skyColor: {type: "string"},
    grassColor: {type: "string"}
  },
  edit: function (props) {
    function updateSkyColor(event) {
      props.setAttributes({skyColor: event.target.value})
    }

    function updateGrassColor(event) {
      props.setAttributes({grassColor: event.target.value})
    }

    return (
      <div>
        <input type="text" placeholder="sky color" value={props.attributes.skyColor} onChange={updateSkyColor} />
        <input type="text" placeholder="grass color" value={props.attributes.grassColor} onChange={updateGrassColor} />
      </div>
    )
  },
  save: function (props) {
    return null
  }
})

index.php:改用 init 钩子注册 Block,把渲染工作交给 PHP 的 render_callback

<?php

/*
  Plugin Name: Are You Paying Attention Quiz
  Description: Give your readers a multiple choice question.
  Version: 1.0
  Author: Brad
  Author URI: https://www.udemy.com/user/bradschiff/
*/

if( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

class AreYouPayingAttention {
  function __construct() {
    add_action('init', array($this, 'adminAssets'));
  }

  function adminAssets() {
    wp_register_script('ournewblocktype', plugin_dir_url(__FILE__) . 'build/index.js', array('wp-blocks', 'wp-element'));
    register_block_type('ourplugin/are-you-paying-attention', array(
      'editor_script' => 'ournewblocktype',
      'render_callback' => array($this, 'theHTML')
    ));
  }

  function theHTML($attributes) {
    ob_start(); ?>
    <h3>Today the sky is <?php echo esc_html($attributes['skyColor']) ?> and the grass is <?php echo esc_html($attributes['grassColor']) ?>!</h3>
    <?php return ob_get_clean();
  }
}

$areYouPayingAttention = new AreYouPayingAttention();

关键改动点:

  • 钩子从 enqueue_block_editor_assets 换成 init因为不再只是「加载一个 JS 文件」这么简单,还要用 PHP 端的 register_block_type() 正式注册这个 Block(PHP 侧的注册要在更早的 init 阶段完成)
  • wp_register_script()(不是 wp_enqueue_script()):只是把这个脚本「登记」在案、取个短名,先不立即加载——真正决定什么时候加载它的,是接下来 register_block_type() 里的 editor_script 参数
  • register_block_type($name, $args):PHP 端注册的名字(ourplugin/are-you-paying-attention)必须跟 JS 里 wp.blocks.registerBlockType() 用的完全一致,两边其实是在描述同一个 Block,只是分工不同——JS 负责编辑器里的交互体验,PHP 负责真正的输出内容
    • editor_script:告诉 WordPress 编辑器加载哪个脚本(也就是刚 wp_register_script() 登记的短名)
    • render_callback这是动态 Block 的核心——指定一个 PHP 函数,前台每次要显示这个 Block 时,WordPress 都会重新调用这个函数、把它的返回值当成最终 HTML 输出,不再依赖数据库里存的静态字符串
  • theHTML($attributes):WordPress 调用这个函数时会自动把「这个 Block 实例的属性值」当参数传进来(是一个普通的 PHP 关联数组,不是 JS 的 props),可以直接用 $attributes['skyColor'] 取值
  • ob_start() / ob_get_clean()(PHP 的输出缓冲区):解决了「用 PHP 拼一长串带很多动态值的字符串很啰嗦」的问题——ob_start() 开始「录制」接下来所有跳出 PHP 直接写的 HTML 内容,中间可以随意用 <?php ... ?> 插入动态值,最后 ob_get_clean() 把刚才录制的内容当作字符串返回并清空缓冲区。这样写法上更接近直接写 HTML 模板,而不是拿 . 一段段拼接字符串
  • 输出动态值前依然要 esc_html() 转义——即使这个场景的攻击面不算大,也延续「凡是动态内容输出到前台就转义」的一贯习惯

Hook / Function 速查

名称类型用途
deprecated(Block 配置属性)Gutenberg Block API保留旧版本 attributes/save 定义,让编辑器能识别并兼容已保存的旧格式内容,避免报错
wp_register_script()WP 内建 function只登记一个脚本的短名和来源,不立即加载,交给别处(如 register_block_typeeditor_script)决定何时加载
register_block_type($name, $args)WP 内建 functionPHP 端注册 Block 类型,render_callback 是实现「动态 Block」的关键参数
ob_start() / ob_get_clean()PHP 内建 function输出缓冲区:捕获一段跳出 PHP 直接写的 HTML,最后当字符串取出,比拼接字符串更易读

常见坑

  • 改了 save 函数的 HTML 结构、又没有提供 deprecated 存档——所有已发布文章里的旧 Block 实例都会报「非预期或无效内容」的错误
  • 误以为「改一次静态字符串型 Block 的 save 函数,所有用到它的文章会自动更新」——实际上必须逐篇手动重新打开保存才会生效,文章一多几乎不可行这正是作者放弃这种做法、改用动态 Block 的核心原因
  • 动态 Block 忘记同时保留 editor_script——render_callback 只负责前台真实输出,编辑器里要能互动编辑(输入框、实时预览)依然需要 JS 的 edit 函数正常加载

[截图:改动 save 函数结构后,编辑器里旧 Block 实例出现的 "This block contains unexpected or invalid content" 错误提示]


延伸 / 后续讲座会用到

基础机制全部打通,下一讲开始做真正实用的 Block:一个带对错判断的互动问答题(Quiz),再往后是能动态关联教授数据的 Featured Professor Block。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 24, EP138-139