gutenbergdocs/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md
2025-10-22 01:40:18 +08:00

286 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

### 在前端实现自定义查询功能
查询循环区块主要通过文章模板区块运作,该区块接收属性并据此构建查询。查询循环区块的其他一级子元素(如分页区块)也以相同方式工作。它们构建查询后,通过 [`query_loop_block_query_vars`](https://developer.wordpress.org/reference/hooks/query_loop_block_query_vars/) 过滤器暴露结果。
您可以通过挂接该过滤器来修改查询。请务必确保不会对其他查询循环区块产生副作用,至少需要检查仅将过滤器应用于您的变体!
```php
if( 'my-plugin/books-list' === $block[ 'attrs' ][ 'namespace' ] ) {
add_filter(
'query_loop_block_query_vars',
function( $query ) {
/** 在此处读取区块的自定义查询参数并构建查询 */
},
);
}
```
(上述代码中,我们假设您有某种访问区块的方式,例如通过 [`pre_render_block`](https://developer.wordpress.org/reference/hooks/pre_render_block/) 过滤器,但具体解决方案可能因使用场景而异,因此这并非硬性建议)。
### 在编辑器端实现自定义查询功能
要完成自定义变体,我们可能希望编辑器能响应自定义查询的更改,并相应显示适当的预览。虽然这对区块功能并非必需,但能为区块使用者提供完全集成的用户体验。
查询循环区块通过 [WordPress REST API](https://developer.wordpress.org/rest-api/) 获取文章以显示预览。任何添加到 `query` 对象的额外参数都将作为查询参数传递给 API。这意味着这些额外参数要么需要 REST API 支持,要么需要通过自定义过滤器处理,例如 [`rest_{$this->post_type}_query`](https://developer.wordpress.org/reference/hooks/rest_this-post_type_query/) 过滤器,该过滤器允许您介入自定义文章类型的任何 API 请求。如下所示:
```php
add_filter(
'rest_book_query',
function( $args, $request ) {
/** 我们可以从这里访问自定义参数 */
$book_author = $request->get_param( 'bookAuthor' );
/** ...您的自定义查询逻辑 */
}
);
```
就这样,您就创建了一个功能完整的查询循环区块变体!
# 扩展查询循环区块
查询循环区块是一个功能强大的工具,允许用户循环遍历指定的文章列表,并显示一系列将继承列表中每篇文章上下文的区块。例如,可以将其设置为循环遍历特定分类下的所有文章,并为每篇文章显示其特色图像。当然,功能远不止于此!
但正因为查询循环区块功能强大且支持高度自定义,它也可能令人望而生畏。大多数用户不希望面对查询循环区块的全部功能,因为他们可能不熟悉“查询”概念及其相关技术术语。相反,大多数用户可能更青睐预设版本的区块,具有更少的设置项和更清晰的命名。默认提供的“文章列表”变体就是这种做法的良好示例:用户可以在不接触技术细节的情况下使用查询循环区块,同时也更有可能发现并理解该区块的用途。
同样地,许多扩展开发者可能需要提供定制化区块版本的方法,这些版本具有自己的预设配置、附加设置,并剔除与其使用场景无关的自定义选项(例如,通常针对其自定义文章类型)。查询循环区块提供了非常强大的方式来创建此类变体。
## 通过变体扩展区块
通过注册具有特定查询循环区块设置的自定义区块变体,您可以更精细地控制其呈现方式,同时仍能使用查询循环区块底层提供的全部功能。如果您不熟悉区块变体,可在此处了解更多信息。
通过区块变体API您可以为您的使用场景提供最合理的默认设置。
要使查询循环变体正常工作,我们需要:
- 为`core/query`区块注册具有某些默认值的区块变体
- 为区块变体定义布局
- 在`isActive`区块变体属性中使用`namespace`属性
让我们以为一个注册了`book`自定义文章类型的插件设置变体为例,开始这段旅程。
### 1. 提供合理的默认值
您的第一步是创建一个变体,该变体将默认显示书籍列表而非博客文章。完整的变体代码如下所示:
```js
const MY_VARIATION_NAME = 'my-plugin/books-list';
registerBlockVariation( 'core/query', {
name: MY_VARIATION_NAME,
title: '书籍列表',
description: '显示书籍列表',
isActive: ( { namespace, query } ) => {
return (
namespace === MY_VARIATION_NAME
&& query.postType === 'book'
);
},
icon: /** 此处可放置SVG图标 */,
attributes: {
namespace: MY_VARIATION_NAME,
query: {
perPage: 6,
pages: 0,
offset: 0,
postType: 'book',
order: 'desc',
orderBy: 'date',
author: '',
search: '',
exclude: [],
sticky: '',
inherit: false,
},
},
scope: [ 'inserter' ],
}
);
```
如果这看起来很多,请不要担心,让我们逐一查看这里的属性,了解它们的存在意义和作用。
本质上,您可以从这样的代码开始:
```js
registerBlockVariation( 'core/query', {
name: 'my-plugin/books-list',
attributes: {
query: {
/** ...更多查询设置(如需要) */
postType: 'book',
},
},
} );
```
通过这种方式,用户无需从下拉菜单中选择自定义的`postType`,即可直接获得正确的配置。但您可能会问,用户如何找到并插入这个变体?好问题!要实现这一点,您应该添加:
```js
{
/** ...变体属性 */
scope: [ 'inserter' ],
}
```
这样,当用户在编辑器中搜索时,您的区块将像任何其他区块一样显示。此时,您可能还想为变体添加自定义图标、标题和描述,如下所示:
```js
{
/** ...变体属性 */
title: '书籍列表',
description: '显示书籍列表',
icon: /* 您的SVG图标在此 */,
}
```
至此,您的自定义变体将与独立区块几乎无法区分。完全为您的插件品牌化,易于发现,并作为即插即用的选项直接提供给用户。
然而,您的查询循环变体尚不能正常工作——我们仍需定义布局以便其正确渲染。
## 扩展查询功能
即便具备上述所有功能,您的自定义文章类型可能仍有独特需求:可能需要支持某些用于筛选和查询的自定义属性,或者某些查询参数可能无关紧要甚至完全不支持!我们在设计查询循环区块时已考虑到这类使用场景,下面来看看如何解决这个问题。
### 禁用无关或不支持的查询控件
假设您的图书数据完全未使用`sticky`属性,那么该设置对区块定制将毫无意义。为避免用户困惑并呈现清晰的交互界面,我们需要隐藏此控件。再假设您完全未使用`author`字段(该字段通常表示将文章添加到数据库的用户),而是使用自定义的`bookAuthor`字段。此时若保留`author`筛选器不仅会造成混淆,更会直接"破坏"您的查询。
为此,查询循环区块变体支持名为`allowedControls`的属性,该属性接收数组参数用于指定要在检查器侧边栏显示的控件键值。默认情况下会显示所有控件,但一旦设置该属性,我们就需要明确指定相关控件!
截至Gutenberg 14.2版本,可用控件如下:
- `inherit` - 显示切换开关,允许查询直接从模板继承
- `postType` - 显示可用文章类型下拉菜单
- `order` - 显示查询排序方式下拉菜单
- `sticky` - 显示置顶文章处理方式下拉菜单
- `taxQuery` - 显示当前所选文章类型的分类法筛选器
- `author` - 显示按作者筛选查询的输入字段
- `search` - 显示按关键词筛选查询的输入字段
- `format` - 显示按[格式集合](https://developer.wordpress.org/advanced-administration/wordpress/post-formats/#supported-formats)筛选查询的输入字段
- `parents` - 显示使用父实体筛选查询的输入字段
在本案例中,该属性配置如下:
```js
{
/** ...变体属性 */
allowedControls: [ 'inherit', 'order', 'taxQuery', 'search' ],
}
```
如需隐藏所有可用控件,可将`allowedControls`值设为空数组。
注意我们同时禁用了`postType`控件。当用户选择我们的变体时,为何还要显示令人困惑的文章类型下拉菜单?更重要的是,正如稍后将介绍的,这可能会破坏我们实现自定义控件的区块功能。
### 添加额外控件
由于我们的插件需要使用自定义属性进行查询,我们需要添加专属控件来替代刚刚从核心检查器禁用的控件。这可以通过接入[区块过滤器](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/)的[React HOC](https://reactjs.org/docs/higher-order-components.html)实现:
```jsx
import { InspectorControls } from '@wordpress/block-editor';
export const withBookQueryControls = ( BlockEdit ) => ( props ) => {
// 仅当属于我们的变体时才添加这些控件
// 此处可实现自定义逻辑进行验证,类似于前述的`isActive`函数
// 以下假设您已编写自定义的`isMyBooksVariation`函数进行处理
return isMyBooksVariation( props ) ? (
<>
<BlockEdit key="edit" { ...props } />
<InspectorControls>
<BookAuthorSelector /> { /** 我们的自定义组件 */ }
</InspectorControls>
</>
) : (
<BlockEdit key="edit" { ...props } />
);
};
addFilter( 'editor.BlockEdit', 'core/query', withBookQueryControls );
```
当然,您需要自行实现控件的逻辑(建议参考[`@wordpress/components`](https://www.npmjs.com/package/@wordpress/components)使控件完美融入Gutenberg界面。通过少量额外工作您在区块属性的`query`对象内设置的任何额外参数,都可用于创建符合需求的自定义查询。
当前您可能需要实现略有差异的路径,以确保在前端(即最终用户侧)正确执行查询,并在编辑器端显示正确的预览。
```js
{
/** ...变体属性 */
attributes: {
/** ...变体属性 */
query: {
/** ...更多查询设置(如需要) */
postType: 'book',
/** 我们的自定义查询参数 */
bookAuthor: 'J. R. R. Tolkien'
}
}
}
```
### 2. 自定义变体布局
请注意,查询循环区块支持在 `scope` 属性中使用字符串 `'block'`。理论上,这是为了让变体在插入区块后被识别。更多关于区块变体选择器的信息请[参阅此处](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-variation-picker/README.md)。
然而,目前**不建议**使用此方式,这是因为查询循环通过模式和 `scope: [ 'block' ]` 变体进行设置时,所选模式的所有属性将被使用,但 `postType``inherit` 查询属性除外,这可能导致冲突和变体功能异常。
为解决此问题,有两种方法。第一种是添加默认的 `innerBlocks`,如下所示:
```js
innerBlocks: [
[
'core/post-template',
{},
[ [ 'core/post-title' ], [ 'core/post-excerpt' ] ],
],
[ 'core/query-pagination' ],
[ 'core/query-no-results' ],
],
```
通过在变体中包含 `innerBlocks`,您实际上跳过了查询循环区块通过建议模式进行设置的阶段,区块将以这些内部区块作为初始内容插入。
另一种方法是为您的变体注册特定模式,这些模式将在设置过程中被推荐,并替换区块的流程。
查询循环区块会判断自身是否有活跃变体,以及该变体是否有特定模式可用。如果有,这些模式将是唯一推荐给用户的模式,不会包含原始查询循环区块的默认模式。否则,如果没有此类模式,将推荐默认模式。
若要将模式与查询循环变体“关联”,您应在模式的 `blockTypes` 属性中添加以查询循环名称前缀的变体名称(例如 `core/query/$variation_name`)。有关注册模式的更多详情,请[参阅此处](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-patterns/)。
如果您未在变体中提供 `innerBlocks`,还有一种方法可以在用户于设置阶段选择 `Start blank` 时推荐“关联”变体。这与“关联”模式的处理方式类似,通过检查查询循环是否有活跃变体以及是否有任何关联变体可推荐。
若要将一个变体与另一个查询循环变体关联,我们需要将 `scope` 属性定义为 `['block']`,并将 `namespace` 属性定义为一个数组。该数组应包含希望关联的变体的名称(`name` 属性)。
例如,如果我们有一个名称为 `products` 的查询循环变体暴露于插入器中(`scope: ['inserter']`),我们可以通过将其 `namespace` 属性设置为 `['products']` 来关联一个作用域为 `block` 的变体。如果用户在点击 `Start blank` 后选择此变体,`namespace` 属性将被主要的插入器变体覆盖。
### 3. 让 Gutenberg 识别您的变体
在实现此变体后,您可能意识到一个小问题:虽然用户在插入时对此无感知,但 Gutenberg 仍会将变体识别为查询循环区块的核心功能,因此在插入后,例如在编辑器的树形视图中,它将显示为查询循环区块。
我们需要一种方式告诉编辑器,此区块实际上是您的特定变体。这正是 `isActive` 属性的用途:它是一种基于区块属性判断特定变体是否活跃的方法。您可以像这样使用它:
```js
{
/** ...变体属性 */
isActive: ( { namespace, query } ) => {
return (
namespace === MY_VARIATION_NAME
&& query.postType === 'book'
);
},
}
```
您可能倾向于仅比较 `postType`,以便当 `postType` 匹配 `book`Gutenberg 会将区块识别为您的变体。然而,这种方式范围过广,因为其他插件可能也希望基于 `book` 文章类型发布变体,或者我们可能不希望用户在编辑器设置中手动将类型设置为 `book` 时每次都识别为变体。
这就是为什么查询循环区块暴露了一个名为 `namespace` 的特殊属性。它在区块实现中实际上没有任何作用,而是作为一种简单且一致的方式供扩展者识别和限定自己的变体。此外,`isActive` 还可以接受一个字符串数组,用于比较属性。通常,`namespace` 就足够了,您可以这样使用:
```js
{
/** ...变体属性 */
attributes: {
/** ...变体属性 */
namespace: 'my-plugin/books-list',
},
isActive: [ 'namespace' ],
}
```
这样Gutenberg 只有在匹配您的自定义命名空间时才会知道这是您的特定变体!非常方便!