EP154. “用 block.json 注册 Block 与 useBlockProps”
🔒 登录后可标记已读改用 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.php:register_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.php里render_callback匹配的那个名字)完全一致,格式是命名空间/block名- 五个资源属性:
editorScript/editorStyle只在后台编辑器加载;script/style在编辑器和前台都加载;viewScript/viewStyle(本讲没用到viewScript,viewStyle干脆不存在)只在前台加载——具体见下面「常见坑」里viewScript的限制 register_block_type()用法改变:第一个参数从「Block 名字字符串」改成指向包含block.json的目录(这里用__DIR__,也就是插件根目录);第二个参数依然可以传render_callback,其余参数(editor_script、editor_style等)都不用再传,因为block.json已经声明过了index.php里原本手动wp_enqueue_script()/wp_enqueue_style()加载前台资源的代码整段删除——改由block.json的script/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.json | WordPress 官方标准配置文件 | 集中声明 Block 的基本信息和资源文件路径,5.8 版本起官方推荐做法 |
register_block_type(目录路径, array) | WP 内建 function | 传入包含 block.json 的目录(而不是 Block 名字字符串)来注册 Block |
useBlockProps(初始props对象) | @wordpress/block-editor Hook | apiVersion: 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跟 PHPrender_callback匹配的 Block 名字对不上——注册会失败或者渲染函数完全不会被调用
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 25, EP154