WordPress 区块编辑器是一个强大的工具,允许你以多种方式创建和格式化内容。它部分由 @wordpress/block-editor 包提供支持,这是一个提供编辑器核心功能的 JavaScript 库。
该包也可用于为几乎任何其他 Web 应用程序创建自定义区块编辑器。这意味着你可以在 WordPress 之外使用相同的区块和区块编辑体验。

这种灵活性和互操作性使区块成为在多个应用程序中构建和管理内容的强大工具。它还简化了开发人员创建最适合其用户的內容编辑器的过程。
本指南介绍如何创建第一个自定义区块编辑器的基础知识。
简介
凭借其众多包和组件,Gutenberg 代码库初看可能令人望而生畏。但其核心在于管理和编辑区块。因此,如果你想从事编辑器工作,了解区块编辑的基本工作原理至关重要。
本指南将带你构建一个在 WordPress 内完全功能的自定义区块编辑器“实例”。在此过程中,我们将向你介绍关键的包和组件,让你了解区块编辑器背后的运作方式。
到本文结束时,你将深刻理解区块编辑器的内部机制,并接近创建自己的区块编辑器实例。
本指南中使用的代码可在 配套 WordPress 插件 中下载。该插件中的演示代码是重要资源。代码语法
本指南中的代码片段使用 JSX 语法。不过,如果你更喜欢,也可以使用纯 JavaScript。但一旦熟悉 JSX,许多开发人员发现它更易读和编写,因此区块编辑器手册中的所有代码示例都使用此语法。
你将构建什么
在本指南中,你将创建一个(几乎)完全功能的区块编辑器实例。结果将类似于这样:

虽然看起来相似,但此编辑器并非你在 WordPress 中创建帖子和页面时熟悉的 区块编辑器。相反,它将是一个完全自定义的实例,存在于名为“区块编辑器”的自定义 WordPress 管理页面中。
该编辑器将具有以下功能:
- 添加和编辑所有核心区块的能力。
- 熟悉的视觉样式和主/侧边栏布局。
- 基础 区块在页面重新加载之间的持久性。
插件设置和组织
自定义编辑器将作为 WordPress 插件构建。为保持简单,插件将命名为 Standalone Block Editor Demo,因为这就是它的功能。
插件文件结构如下所示:

以下是简要说明:
plugin.php– 标准插件“入口”文件,带有注释元数据,需要init.php。init.php– 处理主插件逻辑的初始化。src/(目录)– JavaScript 和 CSS 源文件将存放于此。这些文件 不 由插件直接加载。webpack.config.js– 自定义 Webpack 配置,扩展了@wordpress/scriptsnpm 包提供的默认配置,以允许自定义 CSS 样式(通过 Sass)。
上面未显示的唯一项是 build/ 目录,这是 @wordpress/scripts 输出 编译 JS 和 CSS 文件的位置。这些文件由插件单独加载。
基本文件结构已就位,让我们看看需要哪些包。
编辑器的“核心”
虽然 WordPress 编辑器由许多部分组成,但其核心是 @wordpress/block-editor 包,其功能最好由其自身的 README 文件总结:
此模块允许你创建和使用独立区块编辑器。
完美,这是你将用于创建自定义区块编辑器实例的主要包。但首先,你需要为编辑器创建一个家。
创建自定义“区块编辑器”页面
让我们从在 WordPress 管理后台创建一个托管自定义区块编辑器实例的自定义页面开始。
如果你已经熟悉在 WordPress 中创建自定义管理页面的过程,你可能想 跳过此部分。注册页面
为此,你需要使用标准的 WordPress add_menu_page() 辅助函数 注册一个自定义管理页面:
// File: init.php
add_menu_page(
'Standalone Block Editor', // Visible page name
'Block Editor', // Menu label
'edit_posts', // Required capability
'getdavesbe', // Hook/slug of page
'getdave_sbe_render_block_editor', // Function to render the page
'dashicons-welcome-widgets-menus' // Custom icon
);
getdave_sbe_render_block_editor 函数将用于渲染管理页面的内容。作为提醒,每个步骤的源代码可在 配套插件 中获取。
添加目标 HTML
由于区块编辑器是一个由 React 驱动的应用程序,你需要向自定义页面输出一些 HTML,以便 JavaScript 可以渲染区块编辑器。
让我们使用上面步骤中引用的 getdave_sbe_render_block_editor 函数。
// File: init.php
function getdave_sbe_render_block_editor() {
?>
<div
id="getdave-sbe-block-editor"
class="getdave-sbe-block-editor"
>
Loading Editor...
</div>
<?php
}
该函数输出一些基本占位符 HTML。注意 id 属性 getdave-sbe-block-editor,稍后将使用它。
加载 JavaScript 和 CSS
目标 HTML 已就位,你现在可以加载一些 JavaScript 和 CSS,以便它们在自定义管理页面上运行。
为此,让我们钩入 admin_enqueue_scripts。
首先,你必须确保自定义代码仅在自定义管理页面上运行。因此,在回调函数顶部,如果页面不匹配页面的标识符,则提前退出:
// File: init.php
function getdave_sbe_block_editor_init( $hook ) {
// Exit if not the correct page.
if ( 'toplevel_page_getdavesbe' !== $hook ) {
return;
}
}
add_action( 'admin_enqueue_scripts', 'getdave_sbe_block_editor_init' );
有了这个,你可以安全地使用标准的 WordPress wp_enqueue_script() 函数注册主 JavaScript 文件:
// File: init.php
wp_enqueue_script( $script_handle, $script_url, $script_asset['dependencies'], $script_asset['version'] );
为节省时间和空间,已省略 $script_ 变量赋值。你可以 在此查看。
注意脚本依赖的第三个参数 $script_asset['dependencies']。这些依赖项由
@wordpress/dependency-extraction-webpack-plugin 动态生成,将
确保 构建的包中不包含 WordPress 提供的脚本。
你还需要注册自定义 CSS 样式和 WordPress 默认格式库,以利用一些不错的默认样式:
// File: init.php
// Enqueue default editor styles.
wp_enqueue_style( 'wp-format-library' );
// Enqueue custom styles.
wp_enqueue_style(
'getdave-sbe-styles', // Handle
plugins_url( 'build/index.css', __FILE__ ), // Block editor CSS
array( 'wp-edit-blocks' ), // Dependency to include the CSS after it
filemtime( __DIR__ . '/build/index.css' )
);
内联编辑器设置
查看 @wordpress/block-editor 包,可以看到它接受一个 设置对象 来配置编辑器的默认设置。这些设置在服务器端可用,因此你需要将它们暴露出来以便在 JavaScript 中使用。
为此,让我们 将设置对象以 JSON 格式内联,并分配给全局 window.getdaveSbeSettings 对象:
// File: init.php
// Get custom editor settings.
$settings = getdave_sbe_get_block_editor_settings();
// Inline all settings.
wp_add_inline_script( $script_handle, 'window.getdaveSbeSettings = ' . wp_json_encode( $settings ) . ';' );
注册和渲染自定义块编辑器
有了上述 PHP 代码来创建管理页面,你现在终于可以使用 JavaScript 将块编辑器渲染到页面的 HTML 中了。
首先打开主 src/index.js 文件。然后引入所需的 JavaScript 包并导入 CSS 样式。请注意,使用 Sass 需要 扩展 默认的 @wordpress/scripts Webpack 配置。
// File: src/index.js
import { createRoot } from 'react-dom';
import domReady from '@wordpress/dom-ready';
import { registerCoreBlocks } from '@wordpress/block-library';
import Editor from './editor';
import './styles.scss';
接下来,一旦 DOM 准备就绪,你需要运行一个函数来:
- 从
window.getdaveSbeSettings(之前由 PHP 内联)获取编辑器设置。 - 使用
registerCoreBlocks注册所有核心 WordPress 块。 - 将
<Editor>组件渲染到自定义管理页面上等待的<div>中。
domReady( function () {
const root = createRoot( document.getElementById( 'getdave-sbe-block-editor' ) );
const settings = window.getdaveSbeSettings || {};
registerCoreBlocks();
root.render(
<Editor settings={ settings } />
);
} );
也可以从 PHP 中渲染编辑器,而无需创建不必要的 JS 全局变量。查看 Gutenberg 插件中的 Edit Site 包以获取此示例。审查 <Editor> 组件
让我们仔细看看上面代码中使用的 <Editor> 组件,它位于 配套插件 的 src/editor.js 中。
尽管名字如此,这并非块编辑器的核心。相反,它是一个 包装器 组件,将包含构成自定义编辑器主体的组件。
依赖项
在 <Editor> 内部首先要做的是引入一些依赖项。
// File: src/editor.js
import Notices from 'components/notices';
import Header from 'components/header';
import Sidebar from 'components/sidebar';
import BlockEditor from 'components/block-editor';
其中最重要的是内部组件 BlockEditor 和 Sidebar,稍后将介绍它们。
其余组件主要由构成编辑器布局及周围用户界面 (UI) 的静态元素组成。这些元素包括标题和通知区域等。
编辑器渲染
有了这些组件,你可以定义 <Editor> 组件。
// File: src/editor.js
function Editor( { settings } ) {
return (
<DropZoneProvider>
<div className="getdavesbe-block-editor-layout">
<Notices />
<Header />
<Sidebar />
<BlockEditor settings={ settings } />
</div>
</DropZoneProvider>
);
}
在此过程中,编辑器的布局核心被搭建起来,同时还有几个专门的 上下文提供者 在整个组件层次结构中提供特定功能。
让我们更详细地检查这些:
<DropZoneProvider>– 启用 拖放功能的拖放区域<Notices>– 提供一个“通知栏”通知,如果向core/notices存储区分派了任何消息,则进行渲染<Header>– 在编辑器 UI 顶部渲染静态标题“独立块编辑器”<BlockEditor>– 自定义块编辑器组件
键盘导航
有了这个基本组件结构,唯一剩下的事情就是将所有内容包装在 navigateRegions HOC 中,以在布局中的不同“区域”之间提供键盘导航。
// File: src/editor.js
export default navigateRegions( Editor );
自定义 <BlockEditor>
现在核心布局和组件已到位。是时候探索块编辑器本身的自定义实现了。
该组件称为 <BlockEditor>,这里发生的是魔法。
打开 src/components/block-editor/index.js 会发现这是迄今为止遇到的最复杂的组件。有很多内容,所以首先关注 <BlockEditor> 组件正在渲染的内容:
// File: src/components/block-editor/index.js
return (
<div className="getdavesbe-block-editor">
<BlockEditorProvider
value={ blocks }
onInput={ updateBlocks }
onChange={ persistBlocks }
settings={ settings }
>
<Sidebar.InspectorFill>
<BlockInspector />
</Sidebar.InspectorFill>
<BlockCanvas height="400px" />
</BlockEditorProvider>
</div>
);
关键组件是 <BlockEditorProvider> 和 <BlockList>。让我们检查这些。
理解 <BlockEditorProvider> 组件
<BlockEditorProvider> 是层次结构中最重要组件之一。它为新的块编辑器建立一个新的块编辑上下文。
因此,它是整个项目目标的 基础。
<BlockEditorProvider> 的子项构成块编辑器的 UI。这些组件然后通过 Context 访问数据,使它们能够 渲染 和 管理 编辑器内的块及其行为。
// File: src/components/block-editor/index.js
<BlockEditorProvider
value={ blocks } // 块对象数组
onInput={ updateBlocks } // 管理 Block 更新的处理器
onChange={ persistBlocks } // 管理 Block 更新/持久性的处理器
settings={ settings } // 编辑器“设置”对象
/>
BlockEditor 属性
你可以看到 <BlockEditorProvider> 接受一个 (解析后的) 块对象数组作为其 value 属性,并且当在编辑器内检测到更改时,调用 onChange 和/或 onInput 处理器属性(将新 Blocks 作为参数传递)。
内部它通过订阅提供的 registry(通过 withRegistryProvider HOC)来实现这一点,监听块更改事件,确定块更改是否持久化,然后相应地调用适当的 onChange|Input 处理器。
对于这个简单项目的目的,这些功能允许你:
- 将当前块的数组存储在状态中作为
blocks。 - 在
onInput时通过调用钩子设置器updateBlocks(blocks)更新内存中的blocks状态。 - 使用
onChange处理块到localStorage的基本持久性。这是当块更新被视为“已提交”时 触发的。
同样值得一提的是,该组件接受一个 settings 属性。您可以在 init.php 中将之前内联的编辑器设置以 JSON 格式添加到此位置。您可以使用这些设置来配置自定义颜色、可用图像尺寸以及更多功能。
理解 <BlockList> 组件
除了 <BlockEditorProvider> 之外,下一个最有趣的组件是<BlockList>。
这是最重要的组件之一,因为它的作用是将块列表渲染到编辑器中。
这得益于它被放置在 <BlockEditorProvider> 的子元素位置,从而使其能够完全访问有关编辑器中当前块状态的所有信息。
BlockList 是如何工作的?
在底层,<BlockList> 依赖于其他几个低级组件来渲染块列表。
这些组件的层次结构可以近似如下:
// 仅为示例目的的伪代码。
<BlockList>
/* 从 rootClientId 渲染块列表。 */
<BlockListBlock>
/* 从 BlockList 渲染单个块。 */
<BlockEdit>
/* 渲染块的标准可编辑区域。 */
<Component /> /* 根据其 `edit()` 实现渲染块 UI。
*/
</BlockEdit>
</BlockListBlock>
</BlockList>
以下是这些组件如何协同工作以渲染块列表的大致流程:
<BlockList>遍历所有块的clientIds,并通过<BlockListBlock />渲染每个块。<BlockListBlock />进而使用其自身的子组件<BlockEdit>渲染单个块。- 最后,使用
Component占位符组件渲染块本身。
@wordpress/block-editor 包中的组件是最复杂和复杂的组件之一。如果您想要从根本上理解编辑器的工作原理,了解这些组件至关重要。强烈建议研究这些组件。
审查侧边栏
在 <BlockEditor> 的渲染中,还包括 <Sidebar> 组件。
// 文件:src/components/block-editor/index.js
return (
<div className="getdavesbe-block-editor">
<BlockEditorProvider>
<Sidebar.InspectorFill> /* <-- SIDEBAR */
<BlockInspector />
</Sidebar.InspectorFill>
<BlockCanvas height="400px" />
</BlockEditorProvider>
</div>
);
这用于通过 <BlockInspector> 组件显示高级块设置。
<Sidebar.InspectorFill>
<BlockInspector />
</Sidebar.InspectorFill>
然而,你们中那些目光敏锐的读者可能已经注意到 <Editor> (src/editor.js) 组件的布局中存在一个 <Sidebar> 组件:
// 文件:src/editor.js
<Notices />
<Header />
<Sidebar /> // <-- 这是什么?
<BlockEditor settings={ settings } />
打开 src/components/sidebar/index.js 文件,您可以看到这实际上是上面在 <Editor> 中渲染的组件。然而,该实现利用 Slot/Fill 来暴露一个 Fill (<Sidebar.InspectorFill>),随后在 <BlockEditor> 组件内导入并渲染(见上文)。
有了这个设置,您就可以将 <BlockInspector /> 渲染为 Sidebar.InspectorFill 的子元素。这使您能够在保持 <BlockInspector> 位于 <BlockEditorProvider> 的 React 上下文的同时,允许它在不同的位置(即在 <Sidebar> 中)渲染到 DOM。
这似乎过于复杂,但这是必要的,以便 <BlockInspector> 能够访问有关当前块的信息。没有 Slot/Fill,这种设置将极难实现。
至此,您已经涵盖了自定义 <BlockEditor> 的渲染。
<BlockInspector>本身实际上为
<InspectorControls> 渲染了一个 Slot。这就是允许您渲染 <InspectorControls> 组件并在您的块的 edit() 定义内显示在编辑器的侧边栏中的原因。建议更深入地研究此组件。
块持久化
您在创建自定义块编辑器方面已经走了很长一段路。但还有一个主要领域需要提及——块持久化。换句话说,让您的块在页面刷新之间保存并可用。

由于这只是一个实验,本指南选择利用浏览器的 localStorage API 来处理保存块数据。在现实场景中,您可能会选择更可靠和稳健的系统(例如数据库)。
话虽如此,让我们仔细看看如何处理保存块。
将块存储在状态中
查看 src/components/block-editor/index.js 文件,您会注意到已创建一些状态来将块存储为数组:
// 文件:src/components/block-editor/index.js
const [ blocks, updateBlocks ] = useState( [] );
如前所述,blocks 作为 value 属性传递给“受控”组件 <BlockEditorProvider>。这为其注入了初始块集。同样,updateBlocks setter 已连接到 <BlockEditorProvider> 上的 onInput 回调,这可确保块状态与编辑器中块的更改保持同步。
保存块数据
如果您现在将注意力转向 onChange 处理程序,您会注意到它已连接到名为 persistBlocks() 的函数,该函数的定义如下:
// 文件:src/components/block-editor/index.js
function persistBlocks( newBlocks ) {
updateBlocks( newBlocks );
window.localStorage.setItem( 'getdavesbeBlocks', serialize( newBlocks ) );
}
此函数接受一组“已提交”的块更改,并调用状态 setter updateBlocks。它还将块存储在 LocalStorage 中,键名为 getdavesbeBlocks。为了实现这一点,块数据被序列化为Gutenberg“块语法”格式,这意味着它可以安全地存储为字符串。
如果您打开开发者工具并检查 LocalStorage,您将看到已序列化并在编辑器内发生更改时更新的块数据。以下是格式的示例:
<!-- wp:heading -->
<h2>在 WordPress 后台中独立 Block Editor 的实验</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>这是一个实验,旨在发现创建 WordPress 后台中的独立块编辑器实例有多容易(或困难)。</p>
<!-- /wp:paragraph -->
检索之前的块数据
有了持久化机制固然很好,但如果每次完全页面重新加载时都检索并恢复该数据,则才有用。
访问数据是副作用,因此您必须使用 useEffect 钩子来处理此问题。
// 文件:src/components/block-editor/index.js
useEffect( () => {
const storedBlocks = window.localStorage.getItem( 'getdavesbeBlocks' );
if ( storedBlocks && storedBlocks.length ) {
updateBlocks( () => parse( storedBlocks ) );
createInfoNotice( '块已加载', {
type: 'snackbar',
isDismissible: true,
} );
}
}, [] );
此处理程序:
- 从本地存储中获取序列化的块数据。
- 使用
parse()工具将序列化的块转换回 JavaScript 对象。 - 调用状态设置器
updateBlocks,导致blocks值在状态中被更新,以反映从本地存储检索到的块。
由于这些操作,受控的 <BlockEditorProvider> 组件会根据从本地存储恢复的块进行更新,从而导致编辑器显示这些块。
最后,您将希望生成一个通知——该通知将作为“ Snackbar”通知显示在 <Notice> 组件中——以指示块已恢复。
总结
恭喜您完成本指南。您现在应该对块编辑器的工作原理有了更好的了解。
您刚刚构建的自定义块编辑器的完整代码 可在 GitHub 上获取。请下载并亲自尝试。进行实验,然后更进一步。