社区新闻

useEntityRecords:一种更简便的获取 WordPress 数据的方法

查看官方原文 ↗ 发布于

有很多原因促使您希望从 WordPress 的 REST API 获取数据并在您的应用程序中使用它。

一个常见的例子是创建动态区块时。动态区块没有 save() 函数,而是使用服务器端渲染在前端呈现内容。如果您希望您的区块在编辑器中的外观与在前端相同,那么您的区块的 edit() 函数就需要从 REST API 获取内容。

这只是一个例子。您可能还能想到更多。

@wordpress/data@wordpress/core-data 包提供了许多操作和选择器,使获取数据以及执行其他 CRUD 操作(CRUD 代表创建、读取、更新、删除)变得更加容易。这些操作和选择器的一些例子包括 getEntityRecordseditEntityRecorddeleteEntityRecord 等。

Learn WordPress 上有一个非常好的课程《使用 WordPress 数据层》,它引导您完成使用这些包执行 CRUD 操作的过程。该课程演示了您可能希望获取数据的另一个例子,即在管理页面上显示数据。

遵循该课程中使用的示例,本教程将向您展示如何获取页面列表。但不是在管理页面上显示列表,而是在一个区块中显示它。

在开始之前,您需要有一些页面来显示。最快最简单的方法是导入主题单元测试数据。本教程中的截图将显示区块展示由主题单元测试创建的页面。

创建插件

现在您的数据库中有了一些页面,可以开始构建一个区块来获取它们并在列表中显示页面标题。

@wordpress/create-block 包是快速搭建一个能创建此类区块的插件的好方法。为此,您将创建一个名为 list-pages 的动态区块。在本地测试站点的 wp-content/plugins 目录中运行以下命令:

npx @wordpress/create-block --variant dynamic list-pages

搭建过程需要一两分钟。完成后,激活插件。然后创建一篇新文章,并向文章添加一个“List Pages”区块。

区块插入器截图,显示 List Pages 区块。

保存文章。

您将在编辑器中看到:

截图显示编辑器中新创建的区块。

在前端看到:

截图显示前端新创建的区块。

让我们先从前端视图开始,将其更改为显示页面列表,因为这相当简单。它只是一个获取最近 20 个页面的 WP_Query,然后是一个相当标准的文章循环实现,将页面标题显示在一个无序列表中,即作为 <ul> 中的 <li>,每个都链接到相关页面。如果没有页面,则显示一条消息说明情况。

将此代码添加到 src 目录中的 render.php 文件。(在本教程中,您将专门在 src 目录中工作,因此对于所有未来对文件的引用,您应假定它们在 src 目录中)

<?php
$args = array(
  'post_type' => 'page',
  'posts_per_page' => '20'
);

$pages = new WP_Query( $args );

if ( $pages->have_posts() ) {

  $page_list = '<ul>';

  while ( $pages->have_posts() ) {
    $pages->the_post();
    $page_list .= '<li><a href="'. get_the_permalink() . '">' . get_the_title() . '</a></li>';
  }
  
  $page_list .= '</ul>';

} else {
  $page_list = 'No pages to display.';
}

wp_reset_postdata();
?>

<div <?php echo get_block_wrapper_attributes(); ?>>
  <?php echo $page_list; ?>
</div>

保存文件并从 wp-content/plugins/list-pages 目录内运行 npm start。这将确保更改传播到 build 目录,并监视未来的更改并编译它们。

现在,如果您刷新前端,假设您已按照建议导入了主题单元测试数据,您将看到:

截图显示前端无序的页面标题列表。

顺便说一下,如果您希望您的版本看起来相同,请在 style.scss 中设置 li a 的样式:

.wp-block-create-block-list-pages {

  background-color: #d5e7ee;
  padding: 2px;

  li a {
    color: rgb(32, 81, 167);
  }

}

在编辑器中查看列表

目前编辑器仍然显示与之前相同的内容:

截图显示编辑器中新创建的区块。

理想情况下,编辑器应该看起来像前端。让我们看看如何将相同的内容获取到编辑器中的区块。

让我们从使用 getEntityRecords 开始,就像前面提到的课程中所做的那样。然后您稍后会看到 useEntityRecords 如何使用更少的代码,并使检查查询是否已解析之类的事情变得容易得多。

首先,在 edit.js 中创建一个 <PagesList> 组件:

function PagesList( { pages } ) {

  if ( !pages?.length ) 
    return <div>No pages to display</div>;

  return (
    <ul>
      { pages?.map( page => (
        <li key={ page.id }>
          { page.title.rendered }
        </li>
      ) ) }
    </ul>
  );

}

该组件从传递给它的 props 对象中解构出 pages,并返回一个由从页面数组中获取的标题组成的无序列表。如果数组为空,即没有页面,它将返回字符串“No pages to display”。

当然,该组件需要传递一个页面数组,所以让我们从 WordPress 数据库中获取它。

edit.js 顶部添加这两个导入语句:

import { useSelect } from '@wordpress/data';
import { store as coreDataStore } from '@wordpress/core-data';

接下来,用这个替换 Edit() 函数:

export default function Edit() {

  const pages = useSelect(
    select =>
      select( coreDataStore ).getEntityRecords( 'postType', 'page', { per_page: 20 } ),
    []
  ); 

  return <PagesList pages={ pages }/>;

}

useSelect 是一个从 @wordpress/data 包导入的自定义 React Hook。它接收一个回调函数和一个依赖项数组(在本例中为空)。

回调函数使用存储的 getEntityRecords 函数来检索最近的二十个页面——这是在传递给 getEntityRecords 的第三个参数(查询对象)中定义的。另外两个参数是实体种类,这里是 postType,以及实体名称,在本例中是 page

现在,如果您重新加载编辑器页面,您的区块应该看起来像这样:

截图显示编辑器中无序的页面标题列表。

但是有一个问题!

如果您仔细观察,您会注意到在数据仍在加载时,“No pages to display”文本会出现一小段时间。最好将其替换为加载指示器,以便只有在确实没有页面时才显示“No pages to display”。

添加加载指示器

为此,首先导入 <Spinner> 组件:

import { Spinner } from '@wordpress/components';

接下来,useSelect 回调函数需要返回一个对象。pages 不再是 const 的准确名称,因此将其更改为 data

const data = useSelect(

    select => {
      return {
        pages: select( coreDataStore ).getEntityRecords( 'postType', 'page', { per_page: 20 } ),
        hasResolved: select( coreDataStore ).hasFinishedResolution( 'getEntityRecords', ['postType', 'page', { per_page: 20 } ] )
      }
    },
    []

);

之前 getEntityRecords 查询的结果现在存储在对象的 pages 属性中,并且该对象还有一个额外的属性 hasResolved,它包含由 hasFinishedResolution 返回的布尔值,当查询仍在解析时为 false

将这两个都作为 props 传递给 <PagesList> 组件:

<PagesList hasResolved={ data.hasResolved } pages={ data.pages } />

然后,在 <PagesList> 组件中解构额外的 prop,检查其值,如果为 false 则显示 <Spinner> 组件:

function PagesList( { hasResolved, pages } ) {

  if ( !hasResolved ) 
    return <Spinner />;
  ...

}

您整个 edit.js 文件现在应该看起来像这样:

import { useBlockProps } from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { store as coreDataStore } from '@wordpress/core-data';
import { Spinner } from '@wordpress/components';

export default function Edit() {

  const data = useSelect(
    select => {
      return {
        pages: select( coreDataStore ).getEntityRecords( 'postType', 'page', { per_page: 20 } ),
        hasResolved: select( coreDataStore ).hasFinishedResolution( 'getEntityRecords', ['postType', 'page', { per_page: 20 } ] )
      }
    },
    []
  ); 

  return (
    <div { ...useBlockProps() }>
      <PagesList hasResolved={ data.hasResolved } pages={ data.pages } />
    </div>
  );

}

function PagesList( { hasResolved, pages } ) {

  if ( !hasResolved ) 
    return <Spinner />;
  
  if ( !pages?.length ) 
    return <div>No pages to display</div>;

  return (
    <ul>
      { pages?.map( page => (
        <li key={ page.id }>
          { page.title.rendered }
        </li>
      ) ) }
    </ul>
  );

}

这段代码相当复杂,需要单独的函数调用来获取数据和确定数据是否仍在获取中——两者都需要传递完全相同的查询参数。此外,需要仔细阅读才能正确理解它。

改用 useEntityRecords

现在让我们看看如何通过使用 useEntityRecords 来以更简洁、更优雅、更易于使用的方法实现相同的功能。

useEntityRecords 是一个自定义的 React Hook,它的单数表亲是 useEntityRecord。它们分别是 getEntityRecordsgetEntityRecord 的包装器。这可以从 useEntityRecords源代码中清楚地看到。

React Hook 是在函数组件内部使用的 JavaScript 函数,它“钩入”React 状态和生命周期方法。因此,Hook 可以是有状态的,也可以管理副作用。React 提供了几个内置的 Hook,例如 useStateuseEffect,但也可以在您的应用程序中创建自定义 Hook,这正是 Gutenberg 开发人员所做的,useEntityRecords 就是这样一个自定义 Hook 的例子。

顺便说一下,自定义 React Hook 不要与 WordPress hooks@wordpress/hooks package 混淆。它们是完全不同的东西。

React Hooks 必须在组件的顶层调用,即它们不能在条件语句或循环中使用。还有一些使用 hooks 的其他规则需要牢记。

现在让我们深入了解 useEntityRecords hook,看看使用它如何减少我们需要编写的代码量,以执行类似于上面使用 getEntityRecords 的获取操作。您将继续使用现有的 edit.js 文件。

首先,从 core data 包导入 useEntityRecords

import { useEntityRecords } from '@wordpress/core-data';

您不再需要导入 store,因为它已经被导入到 use-entity-records.ts 中,这是定义 useEntityRecords hook 的文件。而且您将不再使用 useSelect,因此可以删除这两个导入语句:

import { useSelect } from '@wordpress/data';
import { store as coreDataStore } from '@wordpress/core-data';

现在是魔法时刻 🪄。之前使用的 useSelect 语句中有些难以理解的代码现在可以用一行使用 useEntityRecords hook 的代码替换:

const data = useEntityRecords( 'postType', 'page', { per_page: 20 } );

useEntityRecords