EP221. “迁移最后两个 Block:无 render.php 场景与模板 class 清理”
🔒 登录后可标记已读迁移这一章最后两个 Block——genericheading(标题)和 genericbutton(按钮)。这两个 Block 有一个共同的特殊之处:它们从建立之初就没有用 PHP 渲染回调,save 函数直接输出静态 HTML 存进数据库,不需要任何服务器端动态决策——所以 block.json 里完全不需要 render 属性。这一讲也集中处理了一个「用了 apiVersion: 3 之后必然会遇到」的收尾问题:新增的选中态外壳 <div {...blockProps}> 会让编辑器生成的 HTML 多出一层 class(比如 wp-block-ourblocktheme-genericheading),跟模板文件(front-page.html)里写死的旧版 HTML 不匹配,需要手动去模板文件里把这些多余的 class 清理掉,才能让「区块验证」通过。做完这一讲,整个 Block Theme 的全部 Block 都已经迁移到现代化标准做法,旧的 our-blocks/ 文件夹可以彻底删除。
涉及文件
wp-content/themes/fictional-clean-blocks/functions.php(修改,删除整个旧的JSXBlockPHP 类,genericheading/genericbutton切换成register_block_type_from_metadata)wp-content/themes/fictional-clean-blocks/src/genericheading/整套(新建)wp-content/themes/fictional-clean-blocks/src/genericbutton/整套(新建,参照genericheading/复制再改)wp-content/themes/fictional-clean-blocks/templates/front-page.html(修改,清理多余 class)- 删除整个旧的
our-blocks/文件夹
代码实现
src/genericheading/block.json(新建,没有 render 属性):
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "ourblocktheme/genericheading",
"title": "Fictional University Generic Heading",
"attributes": {
"text": { "type": "string" },
"size": { "type": "string", "default": "large" }
},
"editorScript": "file:./index.js"
}
src/genericheading/edit.js(完整文件,从旧的 our-blocks/genericheading.js 搬运):
import { ToolbarGroup, ToolbarButton } from "@wordpress/components"
import { RichText, BlockControls, useBlockProps } from "@wordpress/block-editor"
export default function Edit(props) {
const blockProps = useBlockProps()
function handleTextChange(x) {
props.setAttributes({ text: x })
}
return (
<div {...blockProps}>
<BlockControls>
<ToolbarGroup>
<ToolbarButton
isPressed={props.attributes.size === "large"}
onClick={() => props.setAttributes({ size: "large" })}
>
Large
</ToolbarButton>
<ToolbarButton
isPressed={props.attributes.size === "medium"}
onClick={() => props.setAttributes({ size: "medium" })}
>
Medium
</ToolbarButton>
<ToolbarButton
isPressed={props.attributes.size === "small"}
onClick={() => props.setAttributes({ size: "small" })}
>
Small
</ToolbarButton>
</ToolbarGroup>
</BlockControls>
<RichText
allowedFormats={["core/bold", "core/italic"]}
tagName="h1"
className={`headline headline--${props.attributes.size}`}
value={props.attributes.text}
onChange={handleTextChange}
/>
</div>
)
}
src/genericheading/index.js(完整文件,save 函数直接内联在这里,因为它本来就来自「有 render.php 的 Block 才需要拆分」这条判断标准之外——这里是纯 JS 输出,本来就写在 index.js):
import { RichText } from "@wordpress/block-editor"
import { registerBlockType } from "@wordpress/blocks"
import metadata from "./block.json"
import Edit from "./edit"
function Save(props) {
function createTagName() {
switch (props.attributes.size) {
case "large":
return "h1"
case "medium":
return "h2"
case "small":
return "h3"
}
}
return (
<RichText.Content
tagName={createTagName()}
value={props.attributes.text}
className={`headline headline--${props.attributes.size}`}
/>
)
}
registerBlockType(metadata.name, {
edit: Edit,
save: Save
})
genericbutton:整体结构跟 genericheading 一致,block.json 换成对应的 text/size/linkObject/colorName 属性,edit.js/index.js 分别搬运旧版 genericbutton.js 里的 EditComponent/SaveComponent(内容跟 Block Theme 章节 EP189-191 建立的逻辑一致,此处不重复贴)
functions.php:整个删除旧的 JSXBlock 类,用 register_block_type_from_metadata() 收尾:
register_block_type_from_metadata(__DIR__ . '/build/genericheading');
register_block_type_from_metadata(__DIR__ . '/build/genericbutton');
templates/front-page.html:清理编辑器新生成的多余 class:
<!-- 旧版(迁移前):编辑器/浏览器自动生成的 wp-block-{namespace}-{block名} class -->
<h1 class="wp-block-ourblocktheme-genericheading headline headline--large">Welcome!</h1>
<!-- 新版(清理后,跟模板文件里手写的原始版本一致) -->
<h1 class="headline headline--large">Welcome!</h1>
关键改动点:
genericheading/genericbutton是这套 Block Theme 里唯二「不需要 PHP 渲染回调」的 Block:从最初设计时就决定让它们只输出静态 HTML(save函数算出最终结果、直接存进数据库),不需要任何服务器端动态查询/判断——这类 Block 的block.json里完全不写render属性,少一个步骤- 复制近似 Block 时要小心「复制源本身可能已经不是最简单的了」:这一讲第一次尝试复制一个「简单占位 Block」(比如
archive)当起点,因为这类占位 Blockrender.php是空的、跟genericheading不需要render.php的情况更接近;genericbutton则直接复制刚做完的genericheading文件夹再改 - 踩到的真实 bug:把两个 Block 的
attributes写反了——因为两个文件夹是前后脚复制出来的,内容高度相似,一不小心把genericheading的block.json填成了genericbutton的属性,反之亦然;这类错误没有明显的语法报错,只有在编辑器实际使用时才会暴露(属性对不上导致工具栏/富文本行为跟预期不符),提醒复制文件迁移时要格外确认「改的是不是正确的那一份」 apiVersion: 3强制加的<div {...blockProps}>外壳,会让最终 HTML 多一层wp-block-{命名空间}-{Block名}这个 class(比如wp-block-ourblocktheme-genericheading)——这是 WordPress 自动生成、用来辅助管理 Block 的通用 class;但模板文件(front-page.html)里手写的旧版 HTML 没有这个 class,两者对不上会触发「区块验证失败」的报错(浏览器控制台能看到具体的「预期 HTML vs 实际 HTML」对比)- 修复方式:手动去模板文件里把这个多余 class 删掉,让模板文件里写死的 HTML 跟新版 Block 输出的结构重新对齐——这一步不是自动的,需要过一遍模板文件里所有用到
genericheading/genericbutton的地方,逐个清理 - 别忘了先清除编辑器里保存过的旧自定义版本:跟这一章反复遇到的情况一样,改完模板文件源码后,如果数据库里还存着旧的自定义版本,页面依然会显示旧内容——要先在全站编辑器里「清除自定义项」,确保用的是刚改好的模板文件版本
- 两个 Block 全部迁移完成后,
functions.php里那个曾经承载了整章「手工方案」核心逻辑的JSXBlockPHP 类,可以整个删除——这是这一章从头到尾迁移工作的收官标志:曾经复杂的自定义类被register_block_type_from_metadata()一行代码彻底取代 - 旧的
our-blocks/文件夹此时可以整个删除:所有曾经存在这里的.js/.php文件此时都已经有了对应的src/{block名}/现代化版本,不再需要保留任何旧代码
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
block.json 不写 render 属性 | 配置约定 | 适用于纯 JS/静态 HTML 输出、不需要服务器端动态渲染的 Block |
wp-block-{命名空间}-{Block名}(自动生成的 class) | useBlockProps() 附带效果 | apiVersion: 3 下 WordPress 自动追加的辅助管理 class,可能导致模板文件里手写的旧 HTML 出现验证不匹配 |
常见坑
- 给不需要动态渲染的 Block 也画蛇添足地保留一个空的
render.php并在block.json里声明render属性——虽然不一定报错,但徒增文件、偏离了「纯静态输出不需要 PHP 渲染」这条设计初衷 - 前后脚复制多个相似文件夹时,把不同 Block 各自的
attributes内容改混、张冠李戴——没有语法错误提示,只有在实际使用时才会发现工具栏/富文本行为不对 - 升级到
apiVersion: 3后忘记同步清理模板文件里手写的旧 HTML(缺少wp-block-*这个自动生成的 class)——会导致编辑器提示「区块验证失败」 - 清理完模板文件的 class 之后,忘记先在编辑器里清除已保存的自定义版本——页面继续显示数据库里的旧内容,误以为改动没生效
[截图:模板文件手写 HTML 跟新版 Block 输出的 wp-block-* class 对不上时,编辑器弹出的"区块已被修改/验证失败"提示,含浏览器控制台里预期 HTML 与实际 HTML 的对比]
延伸 / 后续讲座会用到
下一讲要清理构建产物里因为反复复制文件夹产生的多余文件夹(比如 archive copy),并把 package.json 的构建脚本整理成不需要手动罗列所有 Block 入口的简洁版本。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 29, EP221