自定义Gutenberg块中为不同RichText组件配置专属ToolbarButton
问题描述
我有一个内部包含多个RichText组件的自定义Gutenberg块,需要为不同的RichText组件显示特定的ToolbarButton,实现类似原生allowedFormats={['core/italic']}的用户体验——也就是只为指定RichText元素显示对应控件。
我不想用registerFormatType,因为这些控件需要修改块属性而非富文本格式。我尝试过通过焦点状态控制工具栏显示,伪代码如下:
edit : () => { const [isRichText1Focus,setIsRichText1Focus] = useState(false) const [isRichText2Focus,setIsRichText2Focus] = useState(false) return ( <> {isRichText1Focus && ( <BlockControls> <ToolbarGroup> <ToolbarButton icon="test" title="Title" onClick={ () => { // 修改块属性 } } /> </ToolbarGroup> </BlockControls> )} {isRichText2Focus && ( <BlockControls> <ToolbarGroup> <ToolbarButton icon="test 2" title="Title 2" onClick={ () => { // 修改另一个块属性 } } /> </ToolbarGroup> </BlockControls> )} <div className='blockName'> <RichText onBlur={() => { setIsRichText1Focus(false) }} // 其他参数 unstableOnFocus={() => { setIsRichText1Focus(true) }} /> <RichText onBlur={() => { setIsRichText2Focus(false) }} // 其他参数 unstableOnFocus={() => { setIsRichText2Focus(true) }} /> </div> </> ) }
但这个方法有问题:RichText的onBlur无法可靠覆盖点击工具栏本身的情况,导致焦点状态判断失效。
我想知道:能不能复刻Gutenberg原生registerFormatType和formatTypes API的焦点/失焦逻辑?或者有没有更简单的方案?要求必须使用原生Toolbar,不能自定义UI。
解决方案
方法1:利用useSelect监听当前激活的RichText标识符
Gutenberg编辑器内部会跟踪当前激活的RichText组件(通过identifier属性),你可以通过useSelect获取这个状态,从而精准控制工具栏显示:
- 给每个RichText组件添加唯一的
identifier属性 - 使用
useSelect监听editor/rich-text/current-identifier状态 - 根据当前激活的标识符渲染对应工具栏
示例代码:
import { useSelect } from '@wordpress/data'; import { BlockControls, ToolbarGroup, ToolbarButton, RichText } from '@wordpress/block-editor'; import { useState } from '@wordpress/element'; const MyCustomBlock = ({ attributes, setAttributes }) => { // 获取当前激活的RichText标识符 const currentRichTextId = useSelect( ( select ) => { return select( 'core/editor' ).getCurrentRichTextIdentifier(); }, [] ); return ( <> {/* 根据当前激活的RichText显示对应工具栏 */} { currentRichTextId === 'rich-text-1' && ( <BlockControls> <ToolbarGroup> <ToolbarButton icon="edit" title="修改标题属性" onClick={ () => setAttributes({ titleAttr: '新标题' }) } /> </ToolbarGroup> </BlockControls> ) } { currentRichTextId === 'rich-text-2' && ( <BlockControls> <ToolbarGroup> <ToolbarButton icon="text" title="修改内容属性" onClick={ () => setAttributes({ contentAttr: '新内容' }) } /> </ToolbarGroup> </BlockControls> ) } <div className="custom-block"> <RichText identifier="rich-text-1" // 唯一标识符 tagName="h2" value={ attributes.title } onChange={ ( val ) => setAttributes({ title: val }) } placeholder="输入标题" /> <RichText identifier="rich-text-2" // 唯一标识符 tagName="p" value={ attributes.content } onChange={ ( val ) => setAttributes({ content: val }) } placeholder="输入内容" /> </div> </> ); }; export default MyCustomBlock;
方法2:结合useRef和文档焦点事件优化原方案
如果不想依赖编辑器内部状态,可以通过useRef跟踪RichText的DOM元素,结合全局焦点事件判断当前焦点是否在目标RichText或工具栏内:
import { BlockControls, ToolbarGroup, ToolbarButton, RichText } from '@wordpress/block-editor'; import { useState, useRef, useEffect } from '@wordpress/element'; const MyCustomBlock = ({ attributes, setAttributes }) => { const [activeRichText, setActiveRichText] = useState(null); const rt1Ref = useRef(null); const rt2Ref = useRef(null); useEffect(() => { const handleFocusChange = (e) => { // 检查焦点是否在RichText1或当前激活它时的工具栏内 const isRT1Active = rt1Ref.current?.contains(e.target) || document.querySelector('.block-editor-block-controls').contains(e.target) && activeRichText === 'rt1'; // 检查焦点是否在RichText2或当前激活它时的工具栏内 const isRT2Active = rt2Ref.current?.contains(e.target) || document.querySelector('.block-editor-block-controls').contains(e.target) && activeRichText === 'rt2'; if (isRT1Active) { setActiveRichText('rt1'); } else if (isRT2Active) { setActiveRichText('rt2'); } else { setActiveRichText(null); } }; document.addEventListener('focus', handleFocusChange, true); return () => document.removeEventListener('focus', handleFocusChange, true); }, [activeRichText]); return ( <> { activeRichText === 'rt1' && ( <BlockControls> <ToolbarGroup> <ToolbarButton icon="test" title="标题控件" onClick={ () => setAttributes({ titleAttr: '更新' }) } /> </ToolbarGroup> </BlockControls> ) } { activeRichText === 'rt2' && ( <BlockControls> <ToolbarGroup> <ToolbarButton icon="test2" title="内容控件" onClick={ () => setAttributes({ contentAttr: '更新' }) } /> </ToolbarGroup> </BlockControls> ) } <div className="custom-block"> <RichText ref={rt1Ref} tagName="h2" value={ attributes.title } onChange={ (val) => setAttributes({ title: val }) } onFocus={ () => setActiveRichText('rt1') } /> <RichText ref={rt2Ref} tagName="p" value={ attributes.content } onChange={ (val) => setAttributes({ content: val }) } onFocus={ () => setActiveRichText('rt2') } /> </div> </> ); }; export default MyCustomBlock;
方案对比
- 方法1更简洁,完全复用Gutenberg原生状态管理,是推荐方案,无需处理复杂的焦点判断逻辑。
- 方法2适合需要更自定义控制的场景,但需要额外处理焦点事件,兼容性稍差。
内容的提问来源于stack exchange,提问作者Michael Watson
相关产品推荐
相关产品推荐

