在 Gutenberg 中使用侧边栏插件管理 WordPress 元数据

Avatar of Ali Alaa
Ali Alaa

DigitalOcean 为您的旅程的每个阶段提供云产品。立即开始使用 $200 免费积分!

WordPress 发布了他们期待已久的对帖子编辑器的更新,昵称是 Gutenberg,它也被称为块编辑器。它将 WordPress 帖子转换为您可以添加、编辑、删除和重新排列布局的块集合。在正式发布之前,Gutenberg 可以 作为插件使用,在那段时间里,我对学习如何为编辑器创建自定义块很感兴趣。我能够学到很多关于 Gutenberg 的知识,我决定 创建一个课程,它讨论了开发 Gutenberg 块所需了解的几乎所有内容。

在本文中,我们将讨论 WordPress 中的元框和元字段。具体来说,我们将介绍如何在 Gutenberg 中替换旧的 PHP 元框,并扩展 Gutenberg 的侧边栏以添加一个 React 组件,该组件将用于使用全局 JavaScript Redux 式存储来操作元数据。请注意,Gutenberg 中的元数据也可以使用块进行操作。这两种方法都在 我的课程 中进行了讨论,但是,在本文中,我将重点关注在侧边栏中管理元数据,因为我相信这种方法将更常使用。

本文假设您对 ReactJSRedux 有所了解。Gutenberg 严重依赖这些技术来渲染 UI 和管理状态。您还可以查看 CSS-Tricks 学习 Gutenberg 指南,以了解我们将在此处介绍的一些概念的介绍。

块编辑器界面

Gutenberg 是一个 React 应用程序

从本质上讲,Gutenberg 是一个 ReactJS 应用程序。您在编辑器中看到的所有内容都是使用 React 组件渲染的。帖子标题、包含块的内容区域、顶部的工具栏和右侧边栏都是 React 组件。此 React 应用程序中的数据或应用程序状态存储在集中式 JavaScript 对象或“存储”中。这些存储由 WordPress 数据模块 管理。此模块与 Redux 共享许多核心原则。因此,诸如存储、reducer、动作、动作创建者等概念也存在于此模块中。我将有时将这些存储称为“Redux 式”存储。

这些存储不仅存储有关当前帖子的任何数据,例如帖子内容(块)、帖子标题和选定的类别,而且还存储有关 WordPress 网站的全局信息,例如所有类别、标签、帖子、附件等等。除此之外,UI 状态信息(例如,“侧边栏是打开还是关闭?”)也存储在这些全局存储中。“数据模块”的工作之一是从这些存储中检索数据,并更改存储中的数据。由于这些存储是全局的,并且可以由多个 React 组件使用,因此更改任何存储中的数据将反映在使用此数据片段的任何 Gutenberg UI 部分(包括块)中。

保存帖子后,将使用 WordPress REST API 使用存储在这些全局存储中的数据来更新帖子。因此,存储在这些全局存储中的帖子标题、内容、类别等将作为负载发送到更新帖子的 WP REST API 端点。因此,如果我们能够操纵这些存储中的数据,那么一旦用户单击保存,我们操纵的数据将由 API 存储在数据库中,而无需我们执行任何操作。

Gutenberg 中这些全局存储未管理的一件事是元数据。如果您有一些使用 元框 在预 Gutenberg“经典”编辑器中管理的元字段,那么默认情况下,这些元字段不会使用全局 Redux 式存储进行存储和操作。但是,我们可以选择使用 JavaScript 和 Redux 式存储来管理元数据。尽管这些旧的 PHP 元框仍然会出现在 Gutenberg 中,但 WordPress 建议将这些 PHP 元框移植到另一种使用全局存储和 React 组件的方法。这将确保更统一、一致的体验。您可以详细了解 在 Gutenberg 中使用 PHP 元框可能出现的问题

因此,在我们开始之前,让我们看一下 Gutenberg 中的 Redux 式存储以及如何使用它们。

在 Gutenberg 的 Redux 式存储中检索和更改数据

我们现在知道 Gutenberg 页面是使用这些 Redux 式存储管理的。我们有一些默认的“核心”存储,它们由 WordPress 定义。此外,如果我们有一些希望在多个块之间甚至在块和其他 Gutenberg 页面 UI 元素(如侧边栏)之间共享的数据,我们也可以定义自己的存储。创建自己的存储也在 我的课程 中进行了讨论,您可以在 官方文档 中阅读有关它的内容。但是,在本文中,我们将重点介绍如何使用现有存储。使用现有存储使我们能够操作元数据;因此,我们不需要创建任何自定义存储。

为了访问这些存储,请确保您具有最新版本的 WordPress,其中 Gutenberg 处于活动状态,并编辑任何帖子或页面。然后,打开浏览器控制台并键入以下语句

wp.data.select('core/editor').getBlocks()

您应该得到类似这样的内容

让我们分解一下。首先,我们访问 wp.data 模块,该模块(如我们所讨论的)负责管理 Redux 式存储。如果您的 WordPress 安装中包含 Gutenberg,则此模块将在全局 wp 变量中可用。然后,在此模块内部,我们调用一个名为 select 的函数。此函数接收存储名称作为参数,并返回此存储的所有选择器。选择器是数据模块使用的一个术语,它仅仅意味着一个从存储中获取数据的函数。因此,在我们的示例中,我们访问了 core/editor 存储,这将返回一堆函数,这些函数可用于从该存储中获取数据。其中一个函数是 getBlocks(),我们在上面调用了它。此函数将返回一个对象数组,其中每个对象都代表您当前帖子中的一个块。因此,根据您的帖子中有多少个块,此数组将发生变化。

正如我们所看到的,我们访问了一个名为 core/editor 的存储。此存储包含有关您正在编辑的当前帖子的信息。我们还了解了如何获取当前帖子中的块,但我们也可以获取很多其他内容。我们可以获取当前帖子的标题、当前帖子 ID、当前帖子帖子类型,以及我们可能需要的几乎所有其他内容。

但在上面的示例中,我们只能检索数据。如果我们想更改数据怎么办?让我们看一下“core/editor”存储中的另一个选择器。让我们在浏览器控制台中运行此选择器

wp.data.select('core/editor').getEditedPostAttribute('title')

这应该返回当前正在编辑的帖子的标题

太好了!现在,如果我们想使用数据模块更改标题怎么办?我们可以调用 dispatch() 而不是调用 select(),它也接收一个存储名称,并返回一些可以分派的 actions。如果您熟悉 Redux,那么“actions”和“dispatch”之类的术语听起来会很熟悉。如果这听起来很新鲜,那么您需要知道的是,分派某个 action 仅仅意味着更改存储中的某些数据。在我们的例子中,我们想更改存储中的帖子标题,所以我们可以调用这个函数

wp.data.dispatch('core/editor').editPost({title: 'My new title'})

现在看一下编辑器中的帖子标题 - 它将相应地更改!

这就是我们如何操纵 Gutenberg 界面中的任何数据片段的方法。想要检索数据,可以使用选择器;想要更改该数据,可以使用 actions。任何更改都将反映在使用此数据的 UI 的任何部分中。

当然,Gutenberg 中还有其他存储,您可以 在此页面 上查看。因此,在我们继续之前,让我们快速了解一下其他几个存储。

您将使用最多的存储是 core/editor(我们刚刚查看过),以及 core 存储。与 core/editor 不同,core 存储包含的信息不仅限于当前编辑的帖子,还包括整个 WordPress 网站的信息。因此,例如,我们可以使用以下方法获取网站上的所有作者

wp.data.select('core').getAuthors()

我们还可以获取网站上的某些帖子,如下所示

wp.data.select('core').getEntityRecords('postType','post',{per_page: 5})

如果第一个结果是 null,请确保运行两次。一些选择器(如这个选择器)将首先发送一个 API 调用来获取您的帖子。这意味着返回的值最初将是 null,直到 API 请求完成为止

让我们再看一下一个存储:edit-post。此存储负责实际编辑器中的 UI 信息。例如,我们可以有一些选择器来检查侧边栏当前是否打开

wp.data.select('core/edit-post').isEditorSidebarOpened()

如果侧边栏打开,这将返回 true。但请尝试关闭侧边栏,再次运行此函数,它应该返回 false

我们也可以通过在这个商店中调度动作来打开和关闭侧边栏。打开侧边栏并在浏览器控制台中运行这个动作,侧边栏应该会关闭。

wp.data.dispatch('core/edit-post').closeGeneralSidebar()

你不太可能需要使用这个商店,但知道这就是 Gutenberg 在你点击侧边栏图标将其关闭时所做的事情,这一点很重要。

还有一些其他的商店,你可能需要看一下。例如,core/notices 商店可能很有用。这可以帮助你在 Gutenberg 页面中显示错误、警告和成功消息。你也可以查看所有其他商店

尝试在你的浏览器中玩弄这些商店,直到你对使用它们感到满意。之后,我们可以看看如何在浏览器之外的真实代码中使用它们。

让我们设置一个 WordPress 插件来添加一个 Gutenberg 侧边栏

现在我们已经知道如何在 Gutenberg 中使用类似 Redux 的商店,下一步是在编辑器中添加一个 React 侧边栏组件。这个 React 组件将连接到 core/editor 商店,它将有一些输入,当这些输入发生变化时,将调度一些操作来操作元数据 - 就像我们之前操作帖子标题的方式一样。但是要做到这一点,我们需要创建一个包含我们代码的 WordPress 插件。

你可以通过克隆或下载 GitHub 上的这个示例的仓库来进行操作。

让我们在 WordPress 安装的 wp-content/plugins 目录中创建一个新文件夹。我将把它命名为 gutenberg-sidebar。在这个文件夹中,让我们为我们的插件创建入口点。入口点是当激活你的插件时将运行的 PHP 文件。它可以被命名为 index.phpplugin.php。在这个例子中,我们将使用 plugin.php,并在顶部添加一些关于插件的信息,并添加一些代码来避免直接访问。

<?php
/**
  * Plugin Name: gutenberg-sidebar
  * Plugin URI: https://alialaa.com/
  * Description: Sidebar for the block editor.
  * Author: Ali Alaa
  * Author URI: https://alialaa.com/
  */
if( ! defined( 'ABSPATH') ) {
    exit;
}

你应该可以在 WordPress 管理员的插件屏幕上找到你的插件。点击“激活”按钮,让代码运行。

正如你可能想象的那样,从现在开始,我们将编写大量的 JavaScript 和 React 代码。为了更轻松地编写 React 组件,我们需要使用 JSX。而 JSX 不是可以在浏览器中运行的有效 JavaScript,它需要被转换为纯 JavaScript。我们也可能需要使用 ESNext 功能和 导入语句 来导入和导出模块。

这些功能并不能在所有浏览器上运行,所以最好将我们的代码转换为旧的 ES5 JavaScript。幸运的是,有很多工具可以帮助我们实现这一点。一个著名的工具是 webpack。然而,webpack 本身就是一个很大的话题,它不适合本文的范围。因此,我们将使用 WordPress 提供的另一个工具,即 @wordpress/scripts。通过安装这个包,我们将获得一个推荐的 webpack 配置,而无需我们在 webpack 中做任何事情。我个人建议你学习 webpack并尝试自己进行配置。这将帮助你了解发生了什么,并给你更多控制权。你可以在网上找到很多资源,它也在我的课程中讨论过。但现在,让我们安装 WordPress webpack 配置工具。

在终端中切换到你的插件文件夹。

cd path/to/your/theme/folder

接下来,我们需要在这个文件夹中初始化 npm 才能安装 @wordpress/scripts。这可以通过运行以下命令来完成。

npm init

这个命令会问你一些问题,例如包名、版本、许可证等等。你可以一直按 Enter 并使用默认值。你的文件夹中应该有一个 package.json 文件,我们就可以开始安装 npm 包了。让我们通过运行以下命令来安装 @wordpress/scripts

npm install @wordpress/scripts --save-dev

这个包将公开一个名为 wp-scriptsCLI,你可以在你的 npm 脚本中使用它。你可以运行不同的命令。我们现在将重点关注 buildstart 命令。<code>build 脚本将转换你的文件,使它们被最小化并准备好用于生产。你的源代码的入口点在 src/index.js 中配置,转换后的输出将在 build/index.js 中。类似地,start 脚本将把 src/index.js 中的代码转换为 build/index.js,但是这一次,代码不会被最小化,以节省时间和内存 - 这个命令还将监视你的文件中的更改,并在每次更改时重新构建你的文件。start 命令适合用于开发,而 build 命令适合用于生产。要使用这些命令,我们将替换 package.json 文件中的 scripts 键,如果在初始化 npm 时使用了默认选项,它看起来像这样。

将这个

"scripts": {
  "test": "echo "Error: no test specified" && exit 1"
},

…改为这个

"scripts": {
  "start": "wp-scripts start",
  "build": "wp-scripts build"
},

现在我们可以运行 npm startnpm run build 来分别启动开发或构建文件。

让我们在插件的根目录中创建一个名为 src 的新文件夹,并在其中添加一个 index.js 文件。我们可以通过添加一些 JavaScript 代码来查看一切是否正常。我们将尝试使用一个警报。

现在在终端中运行 npm start。你应该发现构建文件夹被创建,其中包含编译后的 index.js 文件和源映射文件。除此之外,你会注意到 build/index.js 文件没有被最小化,并且 webpack 将会监视更改。尝试更改 src/index.js 文件并保存。build/index.js 文件将重新生成。

如果你停止监视(Ctrl + C)在终端中运行 npm run buildbuild/index.js 文件现在应该被最小化了。


现在我们有了 JavaScript 包,我们需要在 Gutenberg 编辑器中加入这个文件。为此,我们可以使用 enqueue_block_editor_assets 钩子,它将确保文件只在 Gutenberg 页面中加入,而不在其他 wp-admin 页面中加入,因为在那里它并不需要。

我们可以在 plugin.php 中像这样加入我们的文件。

// Note that it’s a best practice to prefix function names (e.g. myprefix)
function myprefix_enqueue_assets() {
  wp_enqueue_script(
    'myprefix-gutenberg-sidebar',
    plugins_url( 'build/index.js', __FILE__ )
  );
}
add_action( 'enqueue_block_editor_assets', 'myprefix_enqueue_assets' );

访问 Gutenberg 页面。如果一切顺利,你应该会收到一个警报,这要归功于我们之前在 src/index.js 中添加的内容。

太棒了!我们已经准备好编写一些 JavaScript 代码了,让我们开始吧!

导入 WordPress JavaScript 包

为了向现有的 Gutenberg 侧边栏添加一些内容或创建一个新的空白侧边栏,我们需要注册一个 Gutenberg JavaScript 插件 - 为了做到这一点,我们需要使用 WordPress 提供的包中的一些函数和组件:wp-pluginswp-edit-postwp-i18n。这些包将在浏览器中的 wp 全局变量中作为 wp.pluginswp.editPostwp.i18n 提供。

我们可以将需要的函数导入到 src/index.js 中。具体来说,这些函数是:registerPluginPluginSidebar

const { registerPlugin } = wp.plugins;
const { PluginSidebar } = wp.editPost;
const { __ } = wp.i18n;

值得注意的是,我们需要确保在加入 JavaScript 文件时将这些文件作为依赖项,以确保我们的 index.js 文件将在 wp-pluginswp-edit-postswp-i18n 包之后加载。让我们在 plugin.php 中添加这些依赖项。

function myprefix_enqueue_assets() {
  wp_enqueue_script(
    'myprefix-gutenberg-sidebar',
    plugins_url( 'build/index.js', __FILE__ ),
    array( 'wp-plugins', 'wp-edit-post', 'wp-i18n', 'wp-element' )
  );
}
add_action( 'enqueue_block_editor_assets', 'myprefix_enqueue_assets' );

请注意,我在那里添加了 wp-element 作为依赖项。我这样做是因为我们将使用 JSX 编写一些 React 组件。通常情况下,在创建 React 组件时,我们会导入整个 React 库。然而,wp-element 是 React 之上的一个抽象层,因此我们永远不需要直接安装或导入 React。相反,我们使用 wp-element 作为全局变量。

这些包也作为 npm 包提供。与其从全局 wp 变量(它只在你的代码编辑器不知道的浏览器中可用)中导入函数,不如使用 npm 安装这些包,然后在我们的文件中导入它们。这些 WordPress 包通常以 @wordpress 为前缀。

让我们安装需要的两个包。

npm install @wordpress/edit-post @wordpress/plugins @wordpress/i18n --save

现在我们可以将包导入到 index.js 中。

import { registerPlugin } from "@wordpress/plugins";
import { PluginSidebar } from "@wordpress/edit-post";
import { __ } from "@wordpress/i18n";

以这种方式导入包的优点是,你的文本编辑器知道 @wordpress/edit-post@wordpress/plugins 是什么,它可以为你自动完成函数和组件 - 与从 wp.pluginswp.editPost 中导入不同,它们只在浏览器中可用,而文本编辑器不知道 wp 是什么。

你的文本编辑器可以为你自动完成组件名称。

你可能还会认为在你的包中导入这些包会增加你的包大小,但不要担心。@wordpress/scripts 附带的 webpack 配置文件被指示跳过捆绑这些 @wordpress 包,而是依赖 wp 全局变量。因此,最终的包实际上不会包含各种包,而是通过 wp 变量引用它们。

太好了!所以在这篇文章中,我将坚持使用 npm 导入包,但如果你喜欢,你完全可以从全局 wp 变量中导入。现在让我们使用我们导入的函数吧!

注册一个 Gutenberg 插件

为了在 Gutenberg 中添加一个新的自定义侧边栏,我们首先需要注册一个插件 - 这就是我们导入的 `registerPlugin` 函数的功能。 作为第一个参数,`registerPlugin` 将接收此插件的唯一 slug。 我们可以将一个选项数组作为第二个参数。 在这些选项中,我们可以有一个图标名称(来自 dashicons 库)和一个渲染函数。 此渲染函数可以返回来自 `wp-edit-post` 包的一些组件。 在我们的案例中,我们从 `wp-edit-post` 中导入了 `PluginSidebar` 组件,并在 Gutenberg 编辑器中通过在渲染函数中返回此组件来创建了一个侧边栏。 我还将 `PluginSidebar` 添加到一个 React 片段 中,因为我们也可以在渲染函数中添加其他组件。 此外,从 `wp-i18n` 导入的 `__` 函数将被使用,以便我们可以翻译我们输出的任何字符串。

registerPlugin( 'myprefix-sidebar', {
  icon: 'smiley',
  render: () => {
    return (
      <>
        <PluginSidebar
          title={__('Meta Options', 'textdomain')}
        >
          Some Content
        </PluginSidebar>
      </>
    )
  }
})

你现在应该在 Gutenberg 编辑器屏幕中的齿轮图标旁边看到一个新图标。 这个笑脸图标将切换我们的新侧边栏,侧边栏将包含 `PluginSidebar` 组件中的任何内容。

如果你点击侧边栏标题旁边的星形图标,侧边栏 `smiley` 图标将从顶部的工具栏中移除。 因此,我们需要添加另一种方法来访问我们的侧边栏,以防用户从顶部的工具栏中取消星标,为此,我们可以从 `wp-edit-post` 中导入一个名为 `PluginSidebarMoreMenuItem` 的新组件。 因此,让我们修改我们的导入语句。

import { PluginSidebar, PluginSidebarMoreMenuItem } from "@wordpress/edit-post";

PluginSidebarMoreMenuItem 将允许我们在 Gutenberg 菜单中添加一个项目,你可以使用页面右上角的三个点图标来切换它。 我们想修改我们的插件以包含此组件。 我们需要给 `PluginSidebar` 一个 name 属性,并给 `PluginSidebarMoreMenuItem` 一个 target 属性,其值相同。

registerPlugin( 'myprefix-sidebar', {
  icon: 'smiley',
  render: () => {
    return (
      <>
        <PluginSidebarMoreMenuItem
          target="myprefix-sidebar"
        >
          {__('Meta Options', 'textdomain')}
        </PluginSidebarMoreMenuItem>
        <PluginSidebar
          name="myprefix-sidebar"
          title={__('Meta Options', 'textdomain')}
        >
          Some Content
        </PluginSidebar>
      </>
    )
  }
})

现在在菜单中,我们将有一个 "元选项" 项目,上面有我们的笑脸图标。 此新项目应该切换我们的自定义侧边栏,因为它们使用 name 和 target 属性链接。

太棒了! 现在我们在 Gutenberg 页面中有一个新的空间。 我们可以用 `PluginSidebar` 中的 "some content" 文本替换,并添加我们自己的 React 组件!

此外,让我们确保检查 edit-post 包文档。 此包包含许多其他组件,你可以将其添加到你的插件中。 这些组件可以让你扩展现有的默认侧边栏,并在其中添加你自己的组件。 此外,我们可以找到允许我们在 Gutenberg 右上角菜单以及块菜单中添加项目的组件。

在经典编辑器中处理元数据

让我们快速看一下我们过去是如何使用元框在经典编辑器中管理元数据的。 首先,安装并激活 经典编辑器 插件,以便切换回经典编辑器。 然后,添加一些代码,这些代码将在编辑器页面中添加一个元框。 此元框将管理一个名为 `_myprefix_text_metafield` 的自定义字段。 此元字段只是一个接受 HTML 标记的文本字段。 你可以在 `plugin.php` 中添加此代码,或将其放在单独的文件中并将其包含在 `plugin.php` 中。

<?php
function myprefix_add_meta_box() {
  add_meta_box( 
    'myprefix_post_options_metabox', 
    'Post Options', 
    'myprefix_post_options_metabox_html', 
    'post', 
    'normal', 
    'default'
  );
}
add_action( 'add_meta_boxes', 'myprefix_add_meta_box' );
function myprefix_post_options_metabox_html($post) {
  $field_value = get_post_meta($post->ID, '_myprefix_text_metafield', true);
  wp_nonce_field( 'myprefix_update_post_metabox', 'myprefix_update_post_nonce' );
  ?>
  <p>
    <label for="myprefix_text_metafield"><?php esc_html_e( 'Text Custom Field', 'textdomain' ); ?></label>
    <br />
    <input class="widefat" type="text" name="myprefix_text_metafield" id="myprefix_text_metafield" value="<?php echo esc_attr( $field_value ); ?>" />
  </p>
  <?php
}
function myprefix_save_post_metabox($post_id, $post) {
  $edit_cap = get_post_type_object( $post->post_type )->cap->edit_post;
  if( !current_user_can( $edit_cap, $post_id )) {
    return;
  }
  if( !isset( $_POST['myprefix_update_post_nonce']) || !wp_verify_nonce( $_POST['myprefix_update_post_nonce'], 'myprefix_update_post_metabox' )) {
    return;
  }
  if(array_key_exists('myprefix_text_metafield', $_POST)) {
    update_post_meta( 
      $post_id, 
      '_myprefix_text_metafield', 
      sanitize_text_field($_POST['myprefix_text_metafield'])
    );
  }
}
add_action( 'save_post', 'myprefix_save_post_metabox', 10, 2 );

我不会详细介绍此代码,因为这超出了本文的范围,但它本质上是在做:

  • 使用 add_meta_box 函数创建一个元框。
  • 使用 `myprefix_post_options_metabox_html` 函数渲染 HTML 输入。
  • 控制名为 `_myprefix_text_metafield` 的元字段。
  • 使用 `save_post` 操作钩子获取 HTML 输入值,并使用 update_post_meta 更新字段。

如果你安装了经典编辑器插件,那么你应该在帖子编辑器中看到元字段。

请注意,该字段以一个下划线 (_myprefix_text_metafield) 为前缀,这将阻止它使用 WordPress 中自带的自定义字段元框进行编辑。 我们添加这个下划线是因为我们打算自己管理该字段,并且因为它允许我们将其隐藏在编辑器的标准自定义字段部分。

现在我们有了在经典编辑器中管理该字段的方法,让我们继续停用经典编辑器插件并切换回 Gutenberg。 元框将仍然出现在 Gutenberg 中。 但是,正如我们之前所讨论的,WordPress 建议使用 JavaScript 方法移植此基于 PHP 的元框。

这就是我们在本文剩余部分要做的。 现在我们知道了如何使用 Redux 式存储来操作数据,以及如何在侧边栏中添加一些 React 内容,我们终于可以创建一个 React 组件,它将操作我们的元字段并将其添加到 Gutenberg 编辑器的侧边栏中。

我们不想完全删除基于 PHP 的字段,因为它在我们需要出于某种原因使用经典编辑器的情况下仍然很有用。 因此,我们将隐藏 Gutenberg 激活时的字段,并在经典编辑器激活时显示它。 我们可以通过更新 `myprefix_add_meta_box` 函数来使用 `__back_compat_meta_box` 选项来做到这一点。

function myprefix_add_meta_box() {
  add_meta_box( 
    'myprefix_post_options_metabox', 
    'Post Options', 
    'myprefix_post_options_metabox_html', 
    'post', 
    'normal', 
    'default',
    array('__back_compat_meta_box' => true)
  );
}

让我们继续创建管理元数据的 React 组件。

使用 JavaScript 获取和设置元数据

我们已经了解了如何获取帖子标题以及如何使用 `wp-data` 模块更改它。 让我们看一下如何对自定义字段执行相同的操作。 为了获取元字段,我们可以调用保存选择器 `getEditedPostAttribute`。 但这一次我们将传递一个 `meta` 而不是 `title` 的值。

完成后,在浏览器控制台中进行测试。

wp.data.select('core/editor').getEditedPostAttribute('meta')

你会看到,此函数将返回一个空数组,尽管我们确信我们有一个名为 `_myprefix_text_metafield` 的自定义字段,我们正在使用经典编辑器对其进行管理。 为了使自定义字段可以使用数据模块进行管理,我们首先必须在 `plugin.php` 中注册该字段。

function myprefix_register_meta() {
  register_meta('post', '_myprefix_text_metafield', array(
    'show_in_rest' => true,
    'type' => 'string',
    'single' => true,
  ));
}
add_action('init', 'myprefix_register_meta');

确保将 `show_in_rest` 选项设置为 `true`。 WordPress 将使用 WP REST API 获取字段。 这意味着,我们需要启用 `show_in_rest` 选项才能将其公开。

再次运行控制台测试,我们将获得包含我们所有自定义字段的对象。

太棒了! 我们能够获取自定义字段值,所以现在让我们看一下如何更改存储中的值。 我们可以将 `editPost` 操作分派到 `core/editor` 存储中,并向其传递一个包含 `meta` 键的对象,该对象将是另一个包含我们需要更新的字段的对象。

wp.data.dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: 'new value'}})

现在尝试再次运行 `getEditedPostAttribute` 选择器,该值应该更新为 `new value`。

如果你尝试在使用 Redux 更新字段后保存帖子,你会收到错误。 如果你查看 DevTools 中的网络选项卡,你会发现错误是来自 `wp-json/wp/v2/posts/{id}` REST 端点,它表示我们不允许更新 `_myprefix_text_metafield`。

这是因为 WordPress 将任何以下划线为前缀的字段视为私有值,不能使用 REST API 更新。 但是,我们可以指定一个 `auth_callback` 选项,它将允许在它返回 `true` 时使用 REST API 更新此字段,只要编辑器能够编辑帖子。 我们还可以添加 sanitize_text_field 函数,以便在保存到数据库之前对值进行清理。

function myprefix_register_meta() {
  register_meta('post', '_myprefix_text_metafield', array(
    'show_in_rest' => true,
    'type' => 'string',
    'single' => true,
    'sanitize_callback' => 'sanitize_text_field',
    'auth_callback' => function() { 
      return current_user_can('edit_posts');
    }
  ));
}
add_action('init', 'myprefix_register_meta');

现在尝试以下操作:

  • 在 WordPress 中打开一个新帖子。
  • 在 DevTools 控制台中运行此代码以查看该字段的当前值。
wp.data.select('core/editor').getEditedPostAttribute('meta')
  • 在 DevTools 中运行此代码以更新该值。
wp.data.dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: 'new value'}})
  • 将出现错误,因此保存该帖子以清除错误。
  • 刷新页面并在 DevTools 控制台中运行此代码。
wp.data.select('core/editor').getEditedPostAttribute('meta')

新的值是否在控制台中显示? 如果是,那就太好了! 现在我们知道如何使用 Redux 获取和设置元字段值,我们已经准备好创建一个侧边栏中的 react 组件来执行此操作。

创建一个 React 组件来管理自定义字段

接下来我们需要做的是创建一个 React 组件,该组件包含一个文本字段,该字段由 Redux 存储中元字段的值控制。 它应该具有元字段的值……而且,我们已经知道如何获取它! 我们可以在单独的文件中创建该组件,然后将其导入 `index.js`。 但是,由于我们正在处理一个非常小的示例,我将直接在 `index.js` 中创建。

同样,我们只使用一个文本字段,所以让我们导入 WordPress 包提供的组件,名为 @wordpress/components。 此包包含许多可重用组件,这些组件是 Gutenberg 准备好的,无需我们从头开始编写。 在 order 中使用此包中的组件以与 Gutenberg UI 的其余部分保持一致是个好主意。

首先,让我们安装此包。

npm install --save @wordpress/components

我们将在 `index.js` 的顶部导入 `TextControl` 和 `PanelBody`,以从包中获取我们需要的两个组件。

import { PanelBody, TextControl } from "@wordpress/components";

现在让我们创建我们的组件。 我将创建一个 React 函数组件,并将其命名为 `PluginMetaFields`,但如果你愿意,也可以使用类组件。

let PluginMetaFields = (props) => {
  return (
    <>
      <PanelBody
        title={__("Meta Fields Panel", "textdomain")}
        icon="admin-post"
        intialOpen={ true }
      >
        <TextControl 
          value={wp.data.select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield']}
          label={__("Text Meta", "textdomain")}
        />
      </PanelBody>
    </>
  )
}

PanelBody 接受 `title`、`icon` 和 `initialOpen` 属性。 标题和图标不言自明。 `initialOpen` 默认情况下将面板置于打开/展开状态。 在面板内部,我们有 `TextControl`,它接收输入的标签和值。 正如你在上面的代码段中看到的,我们通过从 `wp.data.select('core/editor').getEditedPostAttribute('meta')` 返回的对象中访问 `_myprefix_text_metafield` 字段来从全局存储中获取值。

请注意,我们现在依赖于 `@wordpress/components` 并使用 `wp.data`。 当我们在 `plugin.php` 中入队我们的文件时,我们必须将这些包添加为依赖项。

function myprefix_enqueue_assets() {
wp_enqueue_script(
    'myprefix-gutenberg-sidebar',
    plugins_url( 'build/index.js', __FILE__ ),
    array( 'wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data' )
  );
}
add_action( 'enqueue_block_editor_assets', 'myprefix_enqueue_assets' );

让我们正式将组件添加到侧边栏,而不是我们之前作为快速示例添加的虚拟文本。

registerPlugin( 'myprefix-sidebar', {
  icon: 'smiley',
  render: () => {
    return (
      <>
        <PluginSidebarMoreMenuItem
          target="myprefix-sidebar"
        >
          {__('Meta Options', 'textdomain')}
        </PluginSidebarMoreMenuItem>
        <PluginSidebar
          name="myprefix-sidebar"
          title={__('Meta Options', 'textdomain')}
        >
          <PluginMetaFields />
        </PluginSidebar>
      </>
    )
  }
})

这应该会给你一个 "元选项" 面板,其中包含一个 "元字段" 标题,一个别针图标,以及一个带有 "测试元" 标签和默认值为 "new value" 的文本输入。

当你输入文本输入框时不会发生任何事情,因为我们还没有处理更新字段。我们将在下一步进行操作,但是,我们首先需要解决另一个问题。尝试在 DevTools 控制台中再次运行 editPost,但使用新值

wp.data.dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: 'a newer value'}})

你会注意到文本字段中的值不会更新为新值。这就是问题所在。我们需要字段由 Redux 存储中的值控制,但我们没有看到它反映在组件中。这是怎么回事呢?

如果你之前在 React 中使用过 Redux,那么你可能知道我们需要使用一个名为 connect 的高阶组件,以便在 React 组件中使用 Redux 存储值。对于 Gutenberg 中的 React 组件也是如此——我们必须使用一些高阶组件来连接我们的组件与 Redux 式存储。不幸的是,我们无法像之前那样直接调用 wp.data.select。这个高阶组件位于 wp.data 全局变量中,它也可以作为名为 @wordpress.data 的 npm 包使用。所以让我们安装它来帮助我们解决这个问题。

npm install --save @wordpress/data

我们需要的高阶组件叫做 withSelect,所以让我们在 index.js 中导入它。

import { withSelect } from "@wordpress/data";

请记住,我们已经在 wp_enqueue_script 中添加了 wp-data 作为依赖项,因此我们可以通过用它包装我们的组件来使用它,如下所示

PluginMetaFields = withSelect(
  (select) => {
    return {
      text_metafield: select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield']
    }
  }
)(PluginMetaFields);

在这里,我们正在覆盖 PluginMetaFields 组件,并为它分配相同的组件,现在用 withSelect 高阶组件包装。withSelect 将接收一个函数作为参数。这个函数将接收 select 函数(我们用来访问 wp.data.select),它应该返回一个对象。该对象中的每个键都将被注入到组件中作为 prop(类似于 Redux 中的 connect)。withSelect 将返回一个函数,我们可以将组件 (PluginMetaFields) 再次传递给它,如上所示。因此,通过拥有这个高阶组件,我们现在在组件中获得了 text_metafield 作为 prop,并且每当 Redux 存储中的元值更新时,prop 也会更新——因此,组件将更新,因为组件会在 prop 更改时更新。

let PluginMetaFields = (props) => {
  return (
    <>
      <PanelBody
        title={__("Meta Fields Panel", "textdomain")}
        icon="admin-post"
        intialOpen={ true }
      >
      <TextControl 
          value={props.text_metafield}
          label={__("Text Meta", "textdomain")}
        />
      </PanelBody>
    </>
  )
}

如果你现在尝试在浏览器中使用新元值运行 editPost,侧边栏中文本字段的值也应该相应更新!

到目前为止,一切顺利。现在我们知道如何将我们的 React 组件连接到我们的 Redux 式存储。现在我们要做的就是在每次我们在文本字段中输入内容时更新存储中的元值。

在 React 组件中分派操作

现在,每当我们在文本字段中输入内容时,我们需要分派 editPost 操作。与 wp.data.select 类似,我们也不应该在我们的组件中直接调用 wp.data.dispatch,如下所示

// Do not do this
<TextControl 
    value={props.text_metafield}
    label={__("Text Meta", "textdomain")}
    onChange={(value) => wp.data.dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: value}})
    }
/>

相反,我们将用 @wordpress.data 包中的另一个高阶组件包装我们的组件,该组件称为 withDispatch。我们必须在 plugin.js 中再次导入它

import { withSelect, withDispatch } from "@wordpress/data";

为了使用它,我们可以包装我们的组件——它已经用 withSelectwithDispatch 包装——如下所示

PluginMetaFields = withDispatch(
  (dispatch) => {
    return {
      onMetaFieldChange: (value) => {
        dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: value}})
      }
    }
  }
)(PluginMetaFields);

你可以查看另一个 WordPress 包,名为 @wordpress/compose。它使在单个组件中使用多个高阶组件变得更加简洁。但我将把它留给你去尝试,以保持我们的示例简单。

withDispatchwithSelect 类似,因为它将接收一个函数,该函数将 dispatch 函数作为参数。这使我们能够从该函数返回一个对象,该对象包含将在组件的 prop 中可用的函数。我通过创建一个具有任意名称 (onMetaFieldChange) 的函数来做到这一点,该函数将接收一个值,分派 editPost 操作,并将 Redux 存储中的元值设置为该函数参数中接收的值。我们可以在组件中调用此函数,并在 onChange 回调中将文本字段的值传递给它

<TextControl 
  value={props.text_metafield}
  label={__("Text Meta", "textdomain")}
  onChange={(value) => props.onMetaFieldChange(value)}
/>

通过打开 WordPress 文章编辑器中的自定义侧边栏,更新字段,保存文章,然后刷新页面以确保值已保存到数据库中来确认一切正常!

让我们添加一个颜色选择器

现在应该很清楚,我们可以使用 JavaScript 更新元字段,但我们到目前为止只关注了简单的文本字段。@wordpress/components 库提供了许多非常有用的组件,包括下拉菜单、复选框、单选按钮等等。让我们升级一下,通过看看如何使用库中包含的颜色选择器组件来结束本教程。

你可能知道该怎么做。首先,我们在 index.js 中导入这个组件

import { PanelBody, TextControl, ColorPicker } from "@wordpress/components";

现在,与其注册一个新的自定义字段,不如为了简单起见,假设这个颜色选择器将由我们之前使用的相同的 _myprefix_text_metafield 字段控制。我们可以在 PanelBody 中使用 ColorPicker 组件,它与我们之前在 TextControl 中看到的非常类似,但 prop 名称略有不同。我们有一个 color prop 而不是 valueonChangeComplete 而不是 onChange。此外,onChangeComplete 将接收一个包含有关所选颜色的一些信息的 color 对象。这个对象将有一个 hex 属性,我们可以用它来存储 _myprefix_text_metafield 字段中的颜色值。

都明白了吗?归根结底就是这个

<ColorPicker
  color={props.text_metafield}
  label={__("Colour Meta", "textdomain")}
  onChangeComplete={(color) => props.onMetaFieldChange(color.hex)}
/>

我们现在应该在侧边栏中有一个颜色选择器,并且由于它控制着与 TextControl 组件相同的元字段,因此每当我们选择新颜色时,我们的旧文本字段也会更新。

总结!

如果你已经读到了这里,那么恭喜你!我希望你喜欢这篇文章。如果你想了解更多关于 Gutenberg 和自定义模块的信息,请务必查看 我的课程。你也可以在 GitHub 上找到本文的最终代码