WP DEVELOP

EP154. “用 block.json 注册 Block 与 useBlockProps”

首页 WordPress 开发课程 GUTENBERG BLOCK 入门(block.json) · EP154
约 18 分钟· #EP154#GUTENBERG BLOCK 入门(block.json)
🔒 登录后可标记已读

改用 block.json 这个 WordPress 官方从 5.8 版本(2021 年)起推荐的标准方式来注册 Block 类型,取代之前手动在 PHP 里调用 register_block_type() 并传一堆参数、手动 wp_enqueue_script()/wp_enqueue_style() 的做法。block.json 能把 Block 的基本信息和资源文件路径都集中写在一份 JSON 里,PHP 和 JS 都不用再重复声明。这一讲也踩了几个坑:改用 apiVersion: 2 后编辑器不再自动帮 Block 包一层「选中态」的外壳 div,需要手动引入 useBlockProps() 补上;viewScript(只在前台加载 JS)这个理论上最合适的属性,只要 Block 用了 PHP 的 render_callback 就完全不会被加载,需要绕道用 script(编辑器和前台都加载)配合 DOMContentLoaded 事件来解决执行时机问题。


涉及文件

  • wp-content/plugins/are-you-paying-attention/block.json (新建)
  • wp-content/plugins/are-you-paying-attention/index.php (修改)
  • wp-content/plugins/are-you-paying-attention/src/index.js (修改)
  • wp-content/plugins/are-you-paying-attention/src/frontend.js (修改)

代码实现

block.json(新建,放在插件根目录)

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 2,
  "name": "ourplugin/are-you-paying-attention",
  "title": "Are You Paying Attention",
  "editorScript": "file:./build/index.js",
  "editorStyle": "file:./build/index.css",
  "script": "file:./build/frontend.js",
  "style": "file:./build/frontend.css"
}

index.phpregister_block_type() 第一个参数改成指向目录,删掉手动 enqueue

<?php

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

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

  function adminAssets() {
    register_block_type(__DIR__, array(
      'render_callback' => array($this, 'theHTML')
    ));
  }

  function theHTML($attributes) {
    ob_start(); ?>
    <div class="paying-attention-update-me"><pre style="display: none;"><?php echo wp_json_encode($attributes) ?></pre></div>
    <?php return ob_get_clean();
  }
}

$areYouPayingAttention = new AreYouPayingAttention();

src/index.js:引入 useBlockProps() 接管选中态外壳

import { InspectorControls, BlockControls, AlignmentToolbar, useBlockProps } from "@wordpress/block-editor"

function EditComponent(props) {
  const blockProps = useBlockProps({
    className: "paying-attention-edit-block",
    style: { backgroundColor: props.attributes.bgColor }
  })

  // ...updateQuestion / deleteAnswer / markAsCorrect 不变...

  return (
    <div {...blockProps}>
      <BlockControls>
        <AlignmentToolbar value={props.attributes.theAlignment} onChange={x => props.setAttributes({ theAlignment: x })} />
      </BlockControls>
      {/* ...InspectorControls / TextControl / 答案列表 不变... */}
    </div>
  )
}

src/frontend.js:把整段渲染逻辑包进 DOMContentLoaded 事件里

import React, { useState, useEffect } from "react"
import ReactDOM from "react-dom"
import "./frontend.scss"

document.addEventListener("DOMContentLoaded", function () {
  const divsToUpdate = document.querySelectorAll(".paying-attention-update-me")

  divsToUpdate.forEach(function (div) {
    const data = JSON.parse(div.querySelector("pre").innerHTML)
    ReactDOM.render(<Quiz {...data} />, div)
    div.classList.remove("paying-attention-update-me")
  })

  function Quiz(props) {
    // ...组件逻辑不变,见 EP150/EP152/EP153...
  }
})

关键改动点:

  • block.json 是什么:WordPress 5.8(2021 年夏)起官方推荐的「标准注册方式」,用一份 JSON 文件集中声明 Block 的基本信息(name/title)和要加载的资源文件路径,PHP 和 JS 里都不需要再重复写这些信息
  • $schema:纯粹给编辑器/IDE 提供字段提示和校验用,不影响功能
  • apiVersion: 2:选择 Block API 的新版本,会带来后面提到的「需要自己接管选中态外壳」这个变化
  • name:必须跟原本 wp.blocks.registerBlockType() 第一个参数(也就是 index.phprender_callback 匹配的那个名字)完全一致,格式是 命名空间/block名
  • 五个资源属性editorScript/editorStyle 只在后台编辑器加载;script/style 在编辑器和前台加载;viewScript/viewStyle(本讲没用到 viewScriptviewStyle 干脆不存在)只在前台加载——具体见下面「常见坑」里 viewScript 的限制
  • register_block_type() 用法改变:第一个参数从「Block 名字字符串」改成指向包含 block.json 的目录(这里用 __DIR__,也就是插件根目录);第二个参数依然可以传 render_callback,其余参数(editor_scripteditor_style 等)都不用再传,因为 block.json 已经声明过了
  • index.php 里原本手动 wp_enqueue_script()/wp_enqueue_style() 加载前台资源的代码整段删除——改由 block.jsonscript/style 属性自动处理
  • apiVersion: 2 的副作用:编辑器不再自动包一层选中态外壳 div。以前(没有指定 apiVersion 或用旧版本时)WordPress 会自动在 Block 外面包一层生成的容器元素、处理好「点击后判断选中状态、加边框」这些逻辑;升级到 apiVersion: 2 之后,这些逻辑需要开发者自己手动接管
  • useBlockProps(初始props对象):从 @wordpress/block-editor 导入的 Hook,调用后会返回一个包含 WordPress 需要的所有属性(点击事件、选中态 class 等)的对象,把原本自己手写在最外层 <div> 上的 className/style 挪进 useBlockProps() 的参数里、让 WordPress 把它们合并进去,而不是各自独立地写在标签上
  • <div {...blockProps}>:用展开语法把 useBlockProps() 返回的所有属性一次性铺到最外层元素上——这一步就是「自己接管选中态外壳」的具体实现,做完之后点击 Block 又能正常显示蓝色选中边框、右侧菜单也能正常触发
  • viewScript 的限制:理论上 viewScript 是最合适的选择(只在前台加载 JS),但只要这个 Block 用了 PHP 的 render_callback 函数(几乎所有实际项目都会用到),WordPress 就完全不会加载 viewScript 指向的文件——这是作者特别强调「不完美」的地方,目前没有绕过这个限制的官方方法
  • 两个应对方案:方案 A 是继续用之前 PHP 手动 wp_enqueue_script() 的老办法(block.json 只负责编辑器脚本,前台脚本依然自己在 render_callback 里手动加载);方案 B(本讲采用)是用 script 属性(编辑器和前台都加载),但这样一来前台 JS 会在页面 <head> 阶段执行,此时 DOM 还没解析完,找不到要渲染的目标元素——用 document.addEventListener("DOMContentLoaded", function() {...}) 把整段渲染逻辑包起来,等 DOM 解析完成后再执行,效果上相当于把脚本挪到了页面底部加载
  • script 而不是 viewScript 的副作用:这份前台 JS 现在也会在编辑器后台加载一份——只要代码写得够「防御性」(比如这里就是典型情况:如果编辑器页面里没有 .paying-attention-update-me 这个 class 的元素,querySelectorAll 会返回空列表,forEach 什么都不会执行,不会报错),就不会有问题

[截图:改用 useBlockProps() 后,Gutenberg 编辑器里点击选中 Quiz Block,正常显示蓝色选中边框和右侧工具栏的效果]


Hook / Function 速查

名称类型用途
block.jsonWordPress 官方标准配置文件集中声明 Block 的基本信息和资源文件路径,5.8 版本起官方推荐做法
register_block_type(目录路径, array)WP 内建 function传入包含 block.json 的目录(而不是 Block 名字字符串)来注册 Block
useBlockProps(初始props对象)@wordpress/block-editor HookapiVersion: 2 下手动接管选中态外壳,返回需要展开到最外层元素上的属性对象
document.addEventListener("DOMContentLoaded", callback)浏览器原生 API等 DOM 解析完成后再执行回调,解决脚本过早执行找不到目标元素的问题

常见坑

  • 用了 apiVersion: 2 却没有引入 useBlockProps()——点击 Block 后不再显示选中边框、右侧工具栏/检查器面板都不会正常出现,因为 WordPress 不再自动处理这层逻辑
  • 指望用 viewScript 只在前台加载 JS——只要 Block 用了 render_callback(几乎所有实际场景都会用),viewScript 完全不会被加载,这是 WordPress 当前版本的已知限制
  • 改用 script 属性后忘记把渲染逻辑包进 DOMContentLoaded——脚本会在 <head> 阶段过早执行,此时 DOM 还没解析,找不到 .paying-attention-update-me 这些目标元素,前台完全不渲染
  • block.json 里的 name 跟 PHP render_callback 匹配的 Block 名字对不上——注册会失败或者渲染函数完全不会被调用

Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 25, EP154