EP184. “RichText 富文本字段与 BlockControls 工具栏按钮”
🔒 登录后可标记已读把 genericheading 从写死的占位文字,改造成一个真正「可以点击编辑文字、还能在悬浮工具栏上选大/中/小尺寸」的自定义标题 Block。核心是 WordPress 提供的 RichText 组件——不用自己实现「可编辑文本」这一整套逻辑(光标、选区、复制粘贴等),直接拿来当输入框用,还能通过 allowedFormats 控制允许哪些格式化选项(加粗/斜体/链接等)。再用 BlockControls + ToolbarGroup + ToolbarButton 在 Block 上方悬浮工具栏里加三个按钮(大/中/小),点击切换 size 属性、驱动标题用不同的 CSS class 和最终输出的标签(h1/h2/h3)。
涉及文件
wp-content/themes/fictional-block-theme/our-blocks/genericheading.js(修改)
代码实现
our-blocks/genericheading.js(完整文件):
import { ToolbarGroup, ToolbarButton } from "@wordpress/components"
import { RichText, BlockControls } from "@wordpress/block-editor"
import { registerBlockType } from "@wordpress/blocks"
registerBlockType("ourblocktheme/genericheading", {
title: "Generic Heading",
attributes: {
text: { type: "string" },
size: { type: "string", default: "large" }
},
edit: EditComponent,
save: SaveComponent
})
function EditComponent(props) {
function handleTextChange(x) {
props.setAttributes({ text: x })
}
return (
<>
<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} />
</>
)
}
function SaveComponent(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}`} />
}
关键改动点:
- 先设计好
attributes:text(字符串,用户输入的标题文字)、size(字符串,默认"large",控制大/中/小)——跟前面章节的经验一致,先想清楚数据形状,再动手写界面 RichText(@wordpress/block-editor):WordPress 提供的富文本编辑字段组件,比自己手写一个<input>/contentEditable强大得多——value绑定当前文字(props.attributes.text),onChange绑定一个处理函数(handleTextChange),每次用户输入/删除字符都会调用一次,拿到最新的完整文字内容handleTextChange(x):跟这门课其他讲座的模式一致,x就是最新的值,函数体内调用props.setAttributes({text: x})更新属性- 必须用 React 片段
<>...</>包住返回内容:因为 JSX 只能有一个顶层元素,而这个组件要同时返回BlockControls(工具栏)和RichText(正文字段)两个平级的部分,用空标签的 Fragment 包起来,不会在真实 DOM 里多出一层无意义的包装元素 tagName(RichText的 prop):决定这个富文本字段渲染成什么 HTML 标签,这里编辑器里固定用"h1"——因为编辑器内部主要是「输入体验」,用什么标签视觉上问题不大;真正要动态区分h1/h2/h3的地方是save阶段真正输出到前台/数据库的内容className用模板字符串动态拼接:`headline headline--${props.attributes.size}——跟这门课其他自定义 Block 一致的「基础 class + 修饰符 class」命名习惯,size是large/medium/small` 中的一个,直接拼进 class 名里allowedFormats={["core/bold", "core/italic"]}:限制这个富文本字段选中文字后弹出的格式化选项,只保留加粗和斜体——不传这个 prop 会显示 WordPress 默认全部选项(加粗、斜体、链接、更多工具等),传空数组[]则会完全没有任何格式化选项BlockControls+ToolbarGroup+ToolbarButton——悬浮工具栏三件套:BlockControls是容器(决定内容显示在 Block 上方悬浮工具栏,跟 Featured Professor 章节学过的InspectorControls/侧栏面板是不同的位置);ToolbarGroup把一组相关按钮框在一起;ToolbarButton是具体每一个可点击按钮<ToolbarButton isPressed={...} onClick={...}>大/中/小</ToolbarButton>:isPressed决定这个按钮当前是否显示成「已选中」的高亮状态(用props.attributes.size === "large"这类判断,跟当前属性值比对);onClick直接内联箭头函数调用props.setAttributes({size: "large"})(或medium/small),三个按钮各自对应一个尺寸值,逻辑完全对称,只是复制三份改一下文字和值save阶段:用RichText.Content(只读渲染)而不是RichText(可编辑)——save函数不需要工具栏和编辑交互,只需要把最终文字和对应标签输出成静态 HTMLcreateTagName()用switch把size映射成对应的 HTML 标签:large → h1、medium → h2、small → h3——这个函数只在save阶段用到(edit阶段的RichText依然固定用h1,纯粹是编辑体验上的简化,不影响实际保存/输出的标签)tagName={createTagName()}:直接调用这个函数、把返回值当tagName的值——跟之前静态字符串写法唯一的区别是这里用{}包一个函数调用表达式,而不是写死的字符串- 修改
save输出内容后,已有的旧 Block 实例会提示「区块已被修改」冲突:因为 Gutenberg 会把当前save函数计算出的 HTML 跟数据库里已经存的旧 HTML 做比对,一旦你改了save逻辑,那些用旧逻辑保存的 Block 实例打开时会报冲突、需要手动点「尝试恢复区块」——这不代表代码写错了,是 Gutenberg 保护数据不被意外覆盖的正常机制,后面章节会学到用「PHP 端渲染(render_callback)」的方式绕开这个问题 - 这一讲的改动只发生在 JS/前台层面,不涉及任何 PHP——所以哪怕反复刷新前台页面,输出内容也不会变,必须回到编辑器里手动重新保存一次模板,新的
save逻辑才会真正生效、把新版 HTML 写回数据库
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
RichText(@wordpress/block-editor) | React 组件 | 提供可编辑的富文本字段,支持 value/onChange/tagName/allowedFormats/className 等 prop |
RichText.Content | React 组件 | RichText 的只读渲染版本,用在 save 函数里输出最终静态 HTML |
BlockControls | React 组件 | 把内容渲染到 Block 上方悬浮工具栏 |
ToolbarGroup / ToolbarButton | React 组件(@wordpress/components) | 悬浮工具栏里的按钮分组容器 / 具体按钮 |
allowedFormats(RichText 的 prop) | 配置项 | 限制富文本字段选中文字后可用的格式化选项(加粗/斜体等) |
常见坑
- 忘记给
EditComponent/SaveComponent声明props参数——没法访问props.attributes/props.setAttributes - 直接返回
BlockControls和RichText两个平级元素而不用 Fragment 包起来——JSX 只允许一个顶层元素,会报错 save阶段依然用RichText(可编辑版本)而不是RichText.Content(只读版本)——save函数的职责是输出最终静态内容,不应该带着可编辑交互逻辑- 改动
save函数的输出结构后,误以为已有的旧 Block 实例出现「区块已被修改」提示是代码写错了——这其实是 Gutenberg 保护已保存数据的正常机制,需要手动处理冲突或者用 PHPrender_callback才能规避 - 忘记 JS 逻辑改动之后需要重新在编辑器里保存一次模板,才会用新版
save逻辑重新生成并写回数据库里的 HTML
[截图:编辑器里点击 Generic Heading Block 后,上方悬浮工具栏出现 Large/Medium/Small 三个按钮,点击可切换标题尺寸的效果]
延伸 / 后续讲座会用到
下一讲会先讲一个大局概念——theme.json 文件如何控制 Block Theme 的整体设置(比如为什么编辑器里 Banner 没有占满整个宽度),再继续做按钮 Block。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 28, EP184