尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Gutenberg 内部组件 BlockActions 全解析:基于 render prop 的块操作逻辑层

Gutenberg 内部组件 BlockActions 全解析:基于 render prop 的块操作逻辑层 Gutenberg 内部组件 BlockActions 全解析基于 render prop 的块操作逻辑层【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenbergBlockActions 是 WordPress 块编辑器Gutenberg中负责对一组块执行操作的核心逻辑组件——它封装了复制、删除、插入、成组/解组、粘贴样式等动作及其可用性判断但不渲染任何 UI而是通过 render prop 将能力交给消费方。本指南以该组件的 README 为骨架结合 index.js 源码与真实消费方 block-settings-dropdown.jsx帮助你彻底掌握它的 Props、可用性标志的计算逻辑、各操作处理器的底层调用链以及在自定义编辑器 UI 中复用的方法。组件定位只提供能力不提供外观BlockActions是 block editor 内部的数据与动作封装层其设计意图非常明确为对一组块执行操作提供处理函数复制、删除、插入、成组、解组、粘贴样式同时给出描述这些操作是否可用的标志。它自身不渲染任何 UI由消费方通过 render prop 自行构建控件。这意味着它是一个典型的逻辑层logic layer组件把能不能做flags和怎么做handlers统一收敛到一处UI 层只需关心渲染。源码 index.js 顶部的引用可以直观印证这一点——它完全由数据层驱动没有任何 JSX 渲染逻辑import { useDispatch, useSelect } from wordpress/data; import { hasBlockSupport, store as blocksStore } from wordpress/blocks; import usePasteStyles from ../use-paste-styles; import { store as blockEditorStore } from ../../store; import { groupBlocks } from ../../utils/group-blocks;重要限制内部组件不可从包外部导入需要特别注意的是BlockActions不会从wordpress/block-editor导出它只能在该包内部通过相对路径引用。因此第三方插件无法通过import { BlockActions } from wordpress/block-editor使用若要在包内复用导入路径形如import BlockActions from ../block-actions该组件必须位于BlockEditorProvider组件树下详见后文使用前提因为其内部依赖 block editor 的 data store 上下文。基本用法render prop 驱动的自定义控件README 给出的最小可用示例展示了如何基于标志条件渲染按钮、并绑定操作处理器import BlockActions from ../block-actions; BlockActions clientIds{ selectedBlockIds } { ( { onDuplicate, canDuplicate, onRemove, canRemove } ) ( { canDuplicate ( button onClick{ onDuplicate }Duplicate/button ) } { canRemove button onClick{ onRemove }Remove/button } / ) } /BlockActions;解读这个模式的三个要点clientIds决定操作目标组件对所有传入的块 ID 统一提供可用性判断与操作入口天然支持多选块场景。render prop 接收一个对象该对象同时包含布尔型可用性标志和函数型操作处理器消费方按需取用、按需渲染。UI 完全归消费方示例中使用原生button你也可以使用wordpress/components的MenuItem、Button等组件构建更复杂的菜单、工具栏。Props 详解clientIds类型String[]作用指定要操作的块的 client ID 数组。它是所有可用性判断与操作的输入基础——源码中getBlocksByClientId( clientIds )、canRemoveBlocks( clientIds )、duplicateBlocks( clientIds, ... )等均以它为参数。children类型Function作用render prop调用时传入单个对象参数。对象包含以下成员可用性标志Availability flags属性类型含义canRemoveBoolean这些块是否可以被删除canDuplicateBoolean每个块是否都支持多实例且能否插入到当前父级中canInsertBlockBoolean是否可以在这些块旁边插入一个新块canCopyStylesBoolean每个块是否都支持颜色或排版从而有样式可供复制操作处理器Action handlers属性类型行为onDuplicateFunction复制这些块返回duplicateBlocksdispatch 的结果onRemoveFunction删除这些块返回removeBlocksdispatch 的结果onInsertBeforeFunction在第一个块之前插入一个默认块onInsertAfterFunction在最后一个块之后插入一个默认块onGroupFunction用包含这些块的单个成组块替换这些块onUngroupFunction用该块的内部块替换该块onCopyFunction闪烁单个选中块以提示其已被复制写入剪贴板由消费方负责onPasteStylesFunction将复制的样式应用到这些块返回Promise__experimentalUpdateSelection类型Boolean默认值true作用复制或删除后是否更新块选区。该值原样透传给duplicateBlocks与removeBlocks动作。从命名可知这是一个实验性 API外部消费时应将其视为不稳定接口避免依赖其长期行为。可用性标志的源码级计算逻辑四个标志全部由 index.js 中的useSelect计算得出理解它们的判定条件才能在自定义 UI 中正确预测按钮的可用状态。canRemove能否删除canRemove: canRemoveBlocks( clientIds ),它直接委托给 block editor store 的canRemoveBlocks选择器。查看 selectors.js 的实现export function canRemoveBlocks( state, clientIds ) { return clientIds.every( ( clientId ) canRemoveBlock( state, clientId ) ); }即所有目标块都必须允许删除canRemoveBlock内部会综合块锁lock.remove、父级模板锁templateLock等约束只要有一个块不可删除canRemove即为false。canDuplicate能否复制canDuplicate: blocks.every( ( block ) { return ( !! block hasBlockSupport( block.name, multiple, true ) canInsertBlockType( block.name, rootClientId ) ); } ),三个条件同时满足块真实存在!! block块类型声明支持多实例——通过 hasBlockSupport 检查multiple支持位默认值为true即默认认为可以复制该块类型能插入到当前父级rootClientId下canInsertBlockType。canInsertBlock能否在旁插入新块const canInsertDefaultBlock canInsertBlockType( getDefaultBlockName(), rootClientId ); const directInsertBlock rootClientId ? getDirectInsertBlock( rootClientId ) : null; // ... canInsertBlock: blocks.every( ( block ) { return ( ( canInsertDefaultBlock || !! directInsertBlock ) canInsertBlockType( block.name, rootClientId ) ); } ),判定要点当前父级能插入默认块或通过getDirectInsertBlock返回了直接插入块见 selectors.js并且目标块自身也能插入到该父级。这保证了插入一个默认块在语义上是可行的。canCopyStyles能否复制样式canCopyStyles: blocks.every( ( block ) { return ( !! block ( hasBlockSupport( block.name, color ) || hasBlockSupport( block.name, typography ) ) ); } ),只要块声明支持color或typography中任意一类即可复制样式——因为样式复制依赖这两个 support 位所对应的属性体系。操作处理器的底层调用链render prop 对象中的八个处理器定义在 index.js全部通过useDispatch( blockEditorStore )获取动作后薄封装。onDuplicate/onRemoveonDuplicate() { return duplicateBlocks( clientIds, updateSelection ); }, onRemove() { return removeBlocks( clientIds, updateSelection ); },它们把__experimentalUpdateSelection作为第二个参数透传给 store 动作。以duplicateBlocks为例actions.js 中的完整逻辑包括空数组或块不存在时提前返回任一目标块不支持multiple时提前返回与canDuplicate的判定一致通过cloneSanitizedBlock深克隆块insertBlocks到最后一个选中块的索引之后若复制出多个块且updateSelection为真则multiSelect全选新块返回新克隆块的 clientId 数组——这正是 README 所说返回duplicateBlocksdispatch 的结果。onInsertBefore/onInsertAfteronInsertBefore() { insertBeforeBlock( clientIds[ 0 ] ); }, onInsertAfter() { insertAfterBlock( clientIds[ clientIds.length - 1 ] ); },分别以首块和末块为锚点调用insertBeforeBlock/insertAfterBlock动作。从 actions.js 可见insertBeforeBlock会优先使用父级blockListSettings中的defaultBlock直接插入块构造新块否则回退到insertDefaultBlock插入默认块。onGroup/onUngrouponGroup() { if ( ! clientIds.length ) return; const groupingBlockName getGroupingBlockName(); const newBlocks groupBlocks( getBlocksByClientId( clientIds ), groupingBlockName ); if ( ! newBlocks ) return; replaceBlocks( clientIds, newBlocks ); }, onUngroup() { if ( ! clientIds.length ) return; const innerBlocks getBlocks( clientIds[ 0 ] ); if ( ! innerBlocks.length ) return; replaceBlocks( clientIds, innerBlocks ); },成组取分组块名默认core/group调用 group-blocks.js 的groupBlocks工具。该工具在分组块的from变换中查找类型为block、isMultiBlock且blocks含通配符*的变换优先使用其__experimentalConvert进行结构性包裹而非块的自身变换——块的自身变换会改变块的身份例如引用块的成组变换会将其拆解为内部块否则回退到switchToBlockType。解组取出目标块的内部块后直接replaceBlocks将父块替换为其子块。onCopy闪烁反馈onCopy() { if ( clientIds.length 1 ) { flashBlock( clientIds[ 0 ] ); } },仅当选中单个块时触发flashBlock做闪烁反馈。注意真正把内容写入剪贴板不是它的职责——正如 README 强调的Writing to the clipboard is the consumers responsibility消费方需自行调用剪贴板 API参见下方真实消费方的CopyMenuItem实现。onPasteStyles粘贴样式async onPasteStyles() { await pasteStyles( getBlocksByClientId( clientIds ) ); },异步执行返回Promise。其底层是usePasteStyles钩子use-paste-styles/index.js完整流程为检查window.navigator.clipboard可用性http:站点除 localhost 外不可用不可用时弹出错误通知readText()读取剪贴板权限被拒时提示allow browser clipboard permissions通过hasSerializedBlocks判断剪贴板文本是否为序列化块纯文本会被解析为core/freeform据此排除解析出源块后按STYLE_ATTRIBUTES列表use-paste-styles/index.jsalign、borderColor、backgroundColor、textAlign、textColor、gradient、className、fontFamily、fontSize、layout、style逐属性过滤——仅当源块与目标块都支持该属性时才应用递归处理内层块并用registry.batch批量提交更新成功时通过noticesStore弹出成功通知单个块显示块标题多个块显示数量。真实消费场景块设置菜单Block Settings DropdownBlockActions最核心的消费方是 block-settings-dropdown.jsx它把BlockActions的能力映射为编辑器右上角选项Options下拉菜单中的各项菜单项Duplicate复制由canDuplicateonDuplicate驱动菜单项上还叠加了updateSelectionAfterDuplicate——复制动作返回新块 ID 后用__experimentalSelectBlock聚焦新块Add before / Add after在前/后添加由canInsertBlock控制显隐绑定onInsertBefore/onInsertAfter并展示对应的键盘快捷键Copy / Cut复制 / 剪切CopyMenuItem使用useCopyToClipboard将serialize( getBlocksByClientId( clientIds ) )写入剪贴板并在回调中调用onCopy()触发闪烁剪切模式则额外调用removeBlocks实现移动语义Copy styles / Paste styles复制/粘贴样式由canCopyStyles控制整组显隐粘贴绑定onPasteStylesDelete删除由canRemove控制绑定onRemove并配合updateSelectionAfterRemove在删除后将焦点/选区转移到前一块、父块或首个块值得注意的细节当canRemove、canDuplicate、canInsertBlock均为false且块处于contentOnly编辑模式时整个菜单会直接渲染null避免出现空菜单。这个例子完整展示了 README 所说的协作模式BlockActions 提供逻辑消费方用DropdownMenu、MenuItem等组件构建 UI。使用前提必须位于 BlockEditorProvider 之下README 的 Related components 一节明确指出block editor 组件用于组合编辑器 UI因此它们只能出现在BlockEditorProvider组件树内参见 provider/README.md。原因从源码即可看出BlockActions大量使用useSelect/useDispatch访问core/block-editorstoregetBlocksByClientId、canRemoveBlocks、duplicateBlocks等而该 store 是由BlockEditorProvider挂载并初始化的。脱离 Provider 使用这些选择器与动作将无数据可依。结语BlockActions是理解 Gutenberg 块编辑器数据层与 UI 层解耦设计的一个极佳样本它用不到两百行代码把多选场景下最常见的八种操作、四种可用性判断以及选区更新策略统一收口再通过 render prop 把全部控制权交还给 UI。掌握它你既能读懂编辑器设置菜单、工具栏等大量 UI 背后的逻辑来源也能在构建自定义块编辑器界面时以同样的模式封装自己的能力层——先想清楚哪些操作可用、各自依赖哪些 store 状态再决定UI 如何呈现。【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表