Gutenberg 块编辑模式(Block Editing Mode)权威指南:从 `useBlockEditingMode` 到编辑器状态管理的完整解析
2026/9/17 5:07:02 网站建设 项目流程

Gutenberg 块编辑模式(Block Editing Mode)权威指南:从useBlockEditingMode到编辑器状态管理的完整解析

【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg

useBlockEditingMode是 Gutenberg 块编辑器(Block Editor)中一个高度专用的 React Hook,它用于读取——或按需设置——单个块的编辑模式。该模式决定了在编辑器中为这一块呈现何种用户界面:是完全禁用编辑、仅保留内容编辑、还是恢复默认的完整编辑体验。本文以 block-editing-mode 组件文档 为核心骨架,深入剖析三种模式(disabledcontentOnlydefault)的语义、继承规则、派生机制,以及从 Hook 到 Store、再到 Reducer 的完整实现链路,并通过仓库内的真实源码与测试用例验证每一项结论,帮助你掌握在自定义块中精准控制编辑体验的实战能力。

一、什么是 Block Editing Mode?

Block Editing Mode 是块编辑器用来约束某个块编辑界面的一类状态。它本身不改变块的内容或保存结果,只决定用户在编辑器中面对什么样的 UI 与交互能力。

从文档定义看,模式共有三个取值:

取值含义
'disabled'完全禁止编辑该块,即该块无法被选中,更不能修改
'contentOnly'隐藏所有非内容 UI,例如工具栏中的辅助控件、块的移动手柄(block movers)、块设置面板等
'default'允许该块正常编辑,即完整的默认编辑体验

文档明确指出,该 Hook 的核心价值在于:模式只约束“界面”,因此非常适合用来为特定块打造“专注内容”的编辑体验——比如在站点编辑器的内容锁定场景下,只允许用户修改段落文本,而隐藏排版、颜色等结构性控件。

1.1 与“块锁定”的关系

Block Editing Mode 与templateLock(模板锁定)和块锁定(Block Lock)是两个紧密相关但职责不同的机制:

  • templateLock: 'contentOnly'是触发派生模式的关键源头(详见第三节);
  • 块锁定(lock属性)控制的是移动、移除等操作权限,而编辑模式控制的是“界面可见性”。

从 store 私有选择器 的注释可以看到两者的明确分工:isBlockLockedByUser这类选择器“只考虑 block lock,不考虑blockEditingMode等可能阻止用户修改块的功能”,原因在于编辑模式是用户无法通过 UI 修改的——它由块自身声明或由系统派生。

二、Hook 实战:读取与设置编辑模式

2.1 读取当前模式(最常见用法)

无参数调用时,Hook 返回当前块的编辑模式,这也是最常用的用法:块根据模式决定是否渲染某些控件。

import { BlockControls, useBlockEditingMode, useBlockProps, } from '@wordpress/block-editor'; function MyBlock( { attributes, setAttributes } ) { const blockEditingMode = useBlockEditingMode(); return ( <> { blockEditingMode === 'default' && ( <BlockControls group="block"> <MyToolbarControl /> </BlockControls> ) } <div { ...useBlockProps() }></div> </> ); }

当模式为'contentOnly''disabled'时,BlockControls中的自定义工具栏控件被隐藏;只有'default'模式才显示完整控件。这一模式在核心块库中被大量采用,例如:

  • 音频块 edit.jsx 中hasNonContentControls = blockEditingMode === 'default',用于决定是否渲染替换媒体等非内容控件;
  • 封面块 edit/index.jsx 同样以blockEditingMode === 'default'判定是否显示非内容控件;
  • 手风琴块 edit.jsx 通过blockEditingMode === 'contentOnly'判断是否处于内容限定模式。

这些用法印证了文档的说法:读取模式、按需隐藏控件,是绝大多数场景下的首选方式。

2.2 设置模式(声明式)

向 Hook 传入一个模式值,即可为该块设置编辑模式:

import { useBlockEditingMode, useBlockProps, } from '@wordpress/block-editor'; function MyBlock( { attributes, setAttributes } ) { useBlockEditingMode( 'disabled' ); return <div { ...useBlockProps() }></div>; }

调用useBlockEditingMode( 'disabled' )后,该块被完全禁止编辑:既不能选中,也无法修改内容。文档强调,传入undefined时当前模式保持不变——这是“读取模式”与“设置模式”共用一个 API 的关键约定。

2.3 参数与返回值速查

项目说明
mode类型String
mode默认值undefined(仅读取,不改变当前模式)
mode可选值'disabled''contentOnly''default'
返回值String,即当前块的实际编辑模式

2.4 源码级实现剖析

Hook 的真实实现位于 block-editing-mode/index.js,其核心逻辑可分为三部分:

  1. 上下文读取:通过useBlockEditContext()取得当前编辑上下文;若上下文中没有clientId(即空字符串),说明调用点不在任何块内部。
  2. 全局模式订阅:仅在clientId为空时,才通过useSelect订阅getBlockEditingMode()选择器获取编辑器根节点的模式,注释明确说明这是“避免不必要的订阅”。
  3. 声明式副作用:当传入mode时,借助useEffect调用setBlockEditingMode( clientId, mode )派发 action;组件卸载或mode变化时,调用unsetBlockEditingMode( clientId )清理该块的模式。两次变更都被标记为__unstableMarkNextChangeAsNotPersistent(),即不进入撤销/重做历史

返回值逻辑同样值得注意:clientId存在时返回context[ blockEditingModeKey ](上下文缓存的模式),否则返回全局模式。这个blockEditingModeKey是一个Symbol,在 block-edit/context.js 中定义,并在 block-edit/index.jsx 中由Edit组件写入上下文,供整棵渲染子树共享。

三、模式的继承与传播规则

3.1 内嵌块:不级联,只有一个例外

文档给出了两条关键规则:

  • 模式不会级联到内嵌块
  • 唯一的例外:一个块被设置为'disabled'时,其内嵌块也会被禁用——除非该内嵌块显式声明了自己的模式。

也就是说:contentOnly不会自动传播给子块,而disabled会向下传播,但显式设置优先级更高。

这一规则在 Reducer 中有精确对应。在 store/reducer.js 的getDerivedBlockEditingModesForTree中:

// Disabled explicit block editing modes are inherited by children. // It's an expensive calculation, so only do it if there are disabled blocks. if ( hasDisabledBlocks ) { // 从父链向上查找最近的显式模式 let ancestorBlockEditingMode; let parent = state.blocks.parents.get( clientId ); while ( parent !== undefined ) { if ( state.blocks.blockEditingModes.has( parent ) ) { ancestorBlockEditingMode = state.blocks.blockEditingModes.get( parent ); } if ( ancestorBlockEditingMode ) { break; } parent = state.blocks.parents.get( parent ); } // 只有祖先是 'disabled' 时才继承 if ( ancestorBlockEditingMode === 'disabled' ) { derivedBlockEditingModes.set( clientId, 'disabled' ); return; } }

代码注释“Disabled explicit block editing modes are inherited by children”与文档规则完全一致,且实现上有两个细节值得注意:

  • 该计算“比较昂贵”,因此仅当状态中确实存在disabled块时hasDisabledBlocks)才执行父链遍历;
  • 父链遍历找到最近的显式模式即停止,且只继承'disabled'contentOnly不会向下传播。

3.2 编辑器根节点:全局模式

如果 Hook 在块上下文之外调用(即没有clientId),模式会被设置到编辑器根节点(clientId = '')。根节点遵循同样的规则:

  • 根节点为'disabled'所有块都被禁用;
  • 根节点为'contentOnly'不会传播到各块。

这与上一节的内嵌规则一致:disabled向下传播、contentOnly不传播。测试用例也覆盖了这一点,例如 selectors.jsdom.test.jsx 中构造了blockEditingModes: new Map( [ [ '', 'disabled' ] ] )的全局禁用状态来验证选择器行为。

3.3 派生模式(Derived Modes)

除了块自身显式声明,模式还可以派生而来。文档指出,在templateLock: 'contentOnly'祖先之下,未显式声明模式的块会被派生出模式:

  • 若该块是内容块(拥有role: 'content'的属性,或声明了supports.contentRole),则派生为'contentOnly'
  • 否则派生为'disabled'
  • 显式声明的模式优先于派生模式

从源码看,这一逻辑实现在 store/reducer.js 的contentOnlyParents分支:

if ( contentOnlyParents.length ) { const hasContentOnlyParent = !! findParentInClientIdsList( state, clientId, contentOnlyParents ); if ( hasContentOnlyParent ) { if ( isContentBlock( blockName ) ) { derivedBlockEditingModes.set( clientId, 'contentOnly' ); } else { derivedBlockEditingModes.set( clientId, 'disabled' ); } } }

而遍历开始处还有一个关键前置判断:

// If the block already has an explicit block editing mode set, // don't override it. if ( state.blocks.blockEditingModes.has( clientId ) ) { return; }

正是这行“显式优先”的保护,实现了文档所说的“An explicitly set mode wins over these derived modes”。

值得注意的是,contentOnlyParents的构成比文档描述更丰富,它聚合了三类来源:

  • contentOnlyTemplateLockedClientIdstemplateLock: 'contentOnly'的块);
  • unsyncedPatternClientIds(非同步模式块,通过attributes.metadata.patternName标识);
  • templatePartClientIds(模板部件,受disableContentOnlyForTemplateParts设置控制)。

也就是说,文档中提到的templateLock: 'contentOnly'是派生模式的主要但非唯一来源——非同步模式和模板部件也参与派生。

3.4 内容块的判定

“内容块”的判定实现在 blocks 包的 utils.ts 的isContentBlock

export function isContentBlock( name: string ): boolean { const blockType = getBlockType( name ); const attributes = blockType?.attributes; // 并非所有块都有属性,但它们可能支持 contentRole。 const supportsContentRole = blockType?.supports?.contentRole; if ( supportsContentRole ) { // ... } // 否则检查是否存在 role: 'content' 的属性 }

该函数被锁定为私有 API(api/index.ts 中的privateApis),供 block-editor 的 Reducer、选择器与私有选择器通过unlock( blocksPrivateApis )调用。相关单元测试位于 utils.jsdom.test.js 的isContentBlock用例,覆盖了“拥有 content role 属性返回 true”与“没有则返回 false”两种情形。

四、Store 层的完整实现链路

4.1 两个状态容器:显式模式与派生模式

从 Reducer 与选择器的结构看,Block Editing Mode 在状态中分两处存储:

  1. state.blocks.blockEditingModes:一个Map<clientId, mode>,记录显式声明的模式(SET_BLOCK_EDITING_MODE/UNSET_BLOCK_EDITING_MODE两个 action 维护);
  2. state.derivedBlockEditingModes:一个Map<clientId, mode>,记录派生的模式(由withDerivedBlockEditingModes高阶 Reducer 维护)。

getBlockEditingMode选择器(selectors.js)按顺序合并两者:

export function getBlockEditingMode( state, clientId = '' ) { if ( clientId === null ) { clientId = ''; } // 先查派生模式(同步模式、缩放模式等场景) if ( state.derivedBlockEditingModes?.has( clientId ) ) { return state.derivedBlockEditingModes.get( clientId ); } // 再查显式模式 if ( state.blocks.blockEditingModes.has( clientId ) ) { return state.blocks.blockEditingModes.get( clientId ); } // 都没有则默认 'default' return 'default'; }

注意这里的查询顺序:派生模式优先于显式模式。这与 Hook 与文档中“显式模式优先”的表述并不矛盾——因为派生模式的产生过程本身就排除了“已显式声明”的块(Reducer 中的if ( state.blocks.blockEditingModes.has( clientId ) ) return;),所以两条路径永远不会对一个块同时生效,选择器的顺序只是兜底保证。

4.2 派生模式的维护:高阶 Reducer

withDerivedBlockEditingModes(store/reducer.js)是一个包裹原 Reducer 的高阶函数,其核心职责是:当块树发生变化时,增量更新派生模式,而不是每次全量重算:

  • REMOVE_BLOCKS:删除被移除子树中所有块的派生模式(先删除、后添加,以正确处理MOVE_BLOCKS_TO_POSITION这类“先删后加”的动作);
  • RECEIVE_BLOCKS/INSERT_BLOCKS:对新插入的块树计算派生模式;
  • UPDATE_BLOCK_ATTRIBUTES:处理非同步模式块的metadata.patternName属性增删,同步更新派生模式(受disableContentOnlyForUnsyncedPatterns设置开关控制)。

此外还有一个特例:SET_EDITOR_MODE动作发生时,即使nextState === state也要重算,因为编辑器模式(如缩放模式 zoomed out)不存储在 block-editor 状态中,改变它不会产生新状态,却会影响派生模式——例如缩放模式下,section 根块派生出'contentOnly',非 section 块派生为'disabled'

4.3 显式模式的维护:Reducer 与 Actions

显式模式由 reducer.js 的blockEditingModes子 Reducer 处理两个 action:

  • SET_BLOCK_EDITING_MODE:写入Map(若模式相同则短路返回原状态);
  • UNSET_BLOCK_EDITING_MODE:从Map中删除。

对应地,actions.js 导出setBlockEditingMode( clientId = '', mode )unsetBlockEditingMode( clientId = '' )两个 action creator,默认clientId都是''(根节点)。Reducer 测试(reducer.js 测试 的blockEditingModes用例)验证了:

  • 默认返回空Map
  • SET_BLOCK_EDITING_MODE正确写入;
  • UNSET_BLOCK_EDITING_MODE正确删除;
  • 重置块时保留仍然存在块的模式、清除已删除块的模式

4.4 显式模式覆盖派生模式的测试佐证

store/test/reducer.js 中有一组非常直接的用例,标题即是文档规则的测试化表达:

  • allows explicitly set blockEditingModes to override the contentOnly template locking——显式模式覆盖contentOnly模板锁定派生的模式;
  • allows explicitly set blockEditingModes to override the unsynced pattern editing modes——覆盖非同步模式派生;
  • allows explicitly set blockEditingModes to override the template part editing modes——覆盖模板部件派生。

这三条用例共同构成了“显式优先”原则的完整测试覆盖。

五、编辑模式如何影响编辑器行为

模式并非孤立的“状态标签”,它被数十个选择器、Hooks 与 UI 组件读取,直接决定编辑器的具体行为。以下是几个有代表性的消费方:

  • canEditBlock/canRemoveBlock/canMoveBlock(selectors.js):当根块的编辑模式为'disabled'时,块不可编辑、不可移动、不可删除,直接阻断结构性的修改操作;
  • isBlockSubtreeDisabled(private-selectors.js):递归判断子树是否整体禁用,供列表视图等场景使用;
  • getEnabledBlockParents(同上):过滤掉'disabled'祖先,用于定位“可编辑的”有效父链;
  • 选择覆盖层判断(selectors.js):当模式不是'default'时返回false,因为“如果模式是'disabled',覆盖层是多余的(块无法被选中);如果是'contentOnly',选中后也没有可交互的控件”——这是 UI 层对模式的直接响应;
  • 插入判断(private-selectors.js 的isContainerInsertableToInContentOnlyMode):contentOnly模式下,容器块只允许插入内容块,且对 section 块有特殊处理(section 块的模式是'default'contentOnly只设置在其子块上);
  • 拖拽排序(actions.js):moveBlocksToPosition会先检查两个块的模式,任一为'disabled'则直接返回,阻止移动;
  • 样式 Hook(hooks/style.jsx):通过useBlockEditingMode()决定是否渲染块样式相关的全局样式控件。

这些消费方的存在说明:编辑模式是一个横跨“可否操作”与“显示什么”两个维度的统一开关

六、常见场景与实战建议

6.1 只读块(禁用编辑)

useBlockEditingMode( 'disabled' );

适合需要“展示但不可交互”的场景,如统计信息、徽章、只读的引用块。注意disabled会连带禁用内嵌块,若某内嵌块需要保留编辑能力,需在其内部显式设置'default'(显式优先于继承)。

6.2 内容专注模式(隐藏结构控件)

useBlockEditingMode( 'contentOnly' );

适合想让用户只关注内容本身的场景:工具栏辅助控件、移动手柄、块设置面板全部隐藏。注意它不会向下传播,子块各自独立判断。

6.3 条件渲染工具栏控件

const blockEditingMode = useBlockEditingMode(); return ( <> { blockEditingMode === 'default' && ( <BlockControls group="block"> <MyToolbarControl /> </BlockControls> ) } <div { ...useBlockProps() }></div> </> );

这是最常见的组合用法:读取模式、按需显示控件,核心块库中大量采用此模式。

6.4 与模板锁定的配合

若要为整棵子树开启内容专注体验,可以在父块上使用templateLock: 'contentOnly':未显式声明模式的子块会被自动派生为contentOnly(内容块)或disabled(非内容块),而显式声明的块不受影响。这是文档明确推荐的组合方式。

七、小结

Block Editing Mode 是 Gutenberg 中“界面约束”与“操作约束”的统一抽象:

  • 三种模式disabled/contentOnly/default)分别对应禁止编辑、内容专注、完整编辑;
  • 继承规则简单但精确:disabled向下传播,contentOnly不传播,显式声明永远优先;
  • 派生机制templateLock: 'contentOnly'、非同步模式、模板部件、缩放模式等场景无需块自身参与即可获得正确的编辑约束;
  • 实现层面useBlockEditingMode(Hook)、getBlockEditingMode(选择器)、blockEditingModesderivedBlockEditingModes(双状态 Map)、withDerivedBlockEditingModes(高阶 Reducer)共同支撑,完整链路可从 block-editing-mode/index.js 一路追踪到 store/reducer.js 与 store/selectors.js。

对自定义块开发者而言,理解这一机制意味着你可以在不破坏编辑器整体一致性的前提下,为特定块精确裁剪编辑体验——既符合 Gutenberg 的设计哲学,也经得起真实用户场景的考验。

【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询