### 在前端实现自定义查询功能

查询循环区块主要通过文章模板区块运作，该区块接收属性并据此构建查询。查询循环区块的其他一级子元素（如分页区块）也以相同方式工作。它们构建查询后，通过 [`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 只有在匹配您的自定义命名空间时才会知道这是您的特定变体！非常方便！