WP DEVELOP

EP184. “RichText 富文本字段与 BlockControls 工具栏按钮”

首页 WordPress 开发课程 BLOCK THEME(2024 最佳实践) · EP184
约 17 分钟· #EP184#BLOCK THEME(2024 最佳实践)
🔒 登录后可标记已读

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}`} />
}

关键改动点:

  • 先设计好 attributestext(字符串,用户输入的标题文字)、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 里多出一层无意义的包装元素
  • tagNameRichText 的 prop):决定这个富文本字段渲染成什么 HTML 标签,这里编辑器里固定用 "h1"——因为编辑器内部主要是「输入体验」,用什么标签视觉上问题不大;真正要动态区分 h1/h2/h3 的地方是 save 阶段真正输出到前台/数据库的内容
  • className 用模板字符串动态拼接:` headline headline--${props.attributes.size} ——跟这门课其他自定义 Block 一致的「基础 class + 修饰符 class」命名习惯,sizelarge/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 函数不需要工具栏和编辑交互,只需要把最终文字和对应标签输出成静态 HTML
  • createTagName()switchsize 映射成对应的 HTML 标签large → h1medium → h2small → 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-editorReact 组件提供可编辑的富文本字段,支持 value/onChange/tagName/allowedFormats/className 等 prop
RichText.ContentReact 组件RichText 的只读渲染版本,用在 save 函数里输出最终静态 HTML
BlockControlsReact 组件把内容渲染到 Block 上方悬浮工具栏
ToolbarGroup / ToolbarButtonReact 组件(@wordpress/components悬浮工具栏里的按钮分组容器 / 具体按钮
allowedFormatsRichText 的 prop)配置项限制富文本字段选中文字后可用的格式化选项(加粗/斜体等)

常见坑

  • 忘记给 EditComponent/SaveComponent 声明 props 参数——没法访问 props.attributes/props.setAttributes
  • 直接返回 BlockControlsRichText 两个平级元素而不用 Fragment 包起来——JSX 只允许一个顶层元素,会报错
  • save 阶段依然用 RichText(可编辑版本)而不是 RichText.Content(只读版本)——save 函数的职责是输出最终静态内容,不应该带着可编辑交互逻辑
  • 改动 save 函数的输出结构后,误以为已有的旧 Block 实例出现「区块已被修改」提示是代码写错了——这其实是 Gutenberg 保护已保存数据的正常机制,需要手动处理冲突或者用 PHP render_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