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

14 KiB
Raw Blame History

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

查询循环区块主要通过文章模板区块运作,该区块接收属性并据此构建查询。查询循环区块的其他一级子元素(如分页区块)也以相同方式工作。它们构建查询后,通过 query_loop_block_query_vars 过滤器暴露结果。

您可以通过挂接该过滤器来修改查询。请务必确保不会对其他查询循环区块产生副作用,至少需要检查仅将过滤器应用于您的变体!

if( 'my-plugin/books-list' === $block[ 'attrs' ][ 'namespace' ] ) {
	add_filter(
		'query_loop_block_query_vars',
		function( $query ) {
			/** 在此处读取区块的自定义查询参数并构建查询 */
		},
	);
}

(上述代码中,我们假设您有某种访问区块的方式,例如通过 pre_render_block 过滤器,但具体解决方案可能因使用场景而异,因此这并非硬性建议)。

在编辑器端实现自定义查询功能

要完成自定义变体,我们可能希望编辑器能响应自定义查询的更改,并相应显示适当的预览。虽然这对区块功能并非必需,但能为区块使用者提供完全集成的用户体验。

查询循环区块通过 WordPress REST API 获取文章以显示预览。任何添加到 query 对象的额外参数都将作为查询参数传递给 API。这意味着这些额外参数要么需要 REST API 支持,要么需要通过自定义过滤器处理,例如 rest_{$this->post_type}_query 过滤器,该过滤器允许您介入自定义文章类型的任何 API 请求。如下所示:

add_filter(
	'rest_book_query',
	function( $args, $request ) {
		/** 我们可以从这里访问自定义参数 */
		$book_author = $request->get_param( 'bookAuthor' );
		/** ...您的自定义查询逻辑 */
	}
);

就这样,您就创建了一个功能完整的查询循环区块变体!

扩展查询循环区块

查询循环区块是一个功能强大的工具,允许用户循环遍历指定的文章列表,并显示一系列将继承列表中每篇文章上下文的区块。例如,可以将其设置为循环遍历特定分类下的所有文章,并为每篇文章显示其特色图像。当然,功能远不止于此!

但正因为查询循环区块功能强大且支持高度自定义,它也可能令人望而生畏。大多数用户不希望面对查询循环区块的全部功能,因为他们可能不熟悉“查询”概念及其相关技术术语。相反,大多数用户可能更青睐预设版本的区块,具有更少的设置项和更清晰的命名。默认提供的“文章列表”变体就是这种做法的良好示例:用户可以在不接触技术细节的情况下使用查询循环区块,同时也更有可能发现并理解该区块的用途。

同样地,许多扩展开发者可能需要提供定制化区块版本的方法,这些版本具有自己的预设配置、附加设置,并剔除与其使用场景无关的自定义选项(例如,通常针对其自定义文章类型)。查询循环区块提供了非常强大的方式来创建此类变体。

通过变体扩展区块

通过注册具有特定查询循环区块设置的自定义区块变体,您可以更精细地控制其呈现方式,同时仍能使用查询循环区块底层提供的全部功能。如果您不熟悉区块变体,可在此处了解更多信息。

通过区块变体API您可以为您的使用场景提供最合理的默认设置。

要使查询循环变体正常工作,我们需要:

  • core/query区块注册具有某些默认值的区块变体
  • 为区块变体定义布局
  • isActive区块变体属性中使用namespace属性

让我们以为一个注册了book自定义文章类型的插件设置变体为例,开始这段旅程。

1. 提供合理的默认值

您的第一步是创建一个变体,该变体将默认显示书籍列表而非博客文章。完整的变体代码如下所示:

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' ],
	}
);

如果这看起来很多,请不要担心,让我们逐一查看这里的属性,了解它们的存在意义和作用。

本质上,您可以从这样的代码开始:

registerBlockVariation( 'core/query', {
	name: 'my-plugin/books-list',
	attributes: {
		query: {
			/** ...更多查询设置(如需要) */
			postType: 'book',
		},
	},
} );

通过这种方式,用户无需从下拉菜单中选择自定义的postType,即可直接获得正确的配置。但您可能会问,用户如何找到并插入这个变体?好问题!要实现这一点,您应该添加:

{
	/** ...变体属性 */
	scope: [ 'inserter' ],
}

这样,当用户在编辑器中搜索时,您的区块将像任何其他区块一样显示。此时,您可能还想为变体添加自定义图标、标题和描述,如下所示:

{
	/** ...变体属性 */
	title: '书籍列表',
	description: '显示书籍列表',
	icon: /* 您的SVG图标在此 */,
}

至此,您的自定义变体将与独立区块几乎无法区分。完全为您的插件品牌化,易于发现,并作为即插即用的选项直接提供给用户。

然而,您的查询循环变体尚不能正常工作——我们仍需定义布局以便其正确渲染。

扩展查询功能

即便具备上述所有功能,您的自定义文章类型可能仍有独特需求:可能需要支持某些用于筛选和查询的自定义属性,或者某些查询参数可能无关紧要甚至完全不支持!我们在设计查询循环区块时已考虑到这类使用场景,下面来看看如何解决这个问题。

禁用无关或不支持的查询控件

假设您的图书数据完全未使用sticky属性,那么该设置对区块定制将毫无意义。为避免用户困惑并呈现清晰的交互界面,我们需要隐藏此控件。再假设您完全未使用author字段(该字段通常表示将文章添加到数据库的用户),而是使用自定义的bookAuthor字段。此时若保留author筛选器不仅会造成混淆,更会直接"破坏"您的查询。

为此,查询循环区块变体支持名为allowedControls的属性,该属性接收数组参数用于指定要在检查器侧边栏显示的控件键值。默认情况下会显示所有控件,但一旦设置该属性,我们就需要明确指定相关控件!

截至Gutenberg 14.2版本,可用控件如下:

  • inherit - 显示切换开关,允许查询直接从模板继承
  • postType - 显示可用文章类型下拉菜单
  • order - 显示查询排序方式下拉菜单
  • sticky - 显示置顶文章处理方式下拉菜单
  • taxQuery - 显示当前所选文章类型的分类法筛选器
  • author - 显示按作者筛选查询的输入字段
  • search - 显示按关键词筛选查询的输入字段
  • format - 显示按格式集合筛选查询的输入字段
  • parents - 显示使用父实体筛选查询的输入字段

在本案例中,该属性配置如下:

{
	/** ...变体属性 */
	allowedControls: [ 'inherit', 'order', 'taxQuery', 'search' ],
}

如需隐藏所有可用控件,可将allowedControls值设为空数组。

注意我们同时禁用了postType控件。当用户选择我们的变体时,为何还要显示令人困惑的文章类型下拉菜单?更重要的是,正如稍后将介绍的,这可能会破坏我们实现自定义控件的区块功能。

添加额外控件

由于我们的插件需要使用自定义属性进行查询,我们需要添加专属控件来替代刚刚从核心检查器禁用的控件。这可以通过接入区块过滤器React HOC实现:

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使控件完美融入Gutenberg界面。通过少量额外工作您在区块属性的query对象内设置的任何额外参数,都可用于创建符合需求的自定义查询。

当前您可能需要实现略有差异的路径,以确保在前端(即最终用户侧)正确执行查询,并在编辑器端显示正确的预览。

{
	/** ...变体属性 */
	attributes: {
		/** ...变体属性 */
		query: {
			/** ...更多查询设置(如需要) */
			postType: 'book',
			/** 我们的自定义查询参数 */
			bookAuthor: 'J. R. R. Tolkien'
		}
	}
}

2. 自定义变体布局

请注意,查询循环区块支持在 scope 属性中使用字符串 'block'。理论上,这是为了让变体在插入区块后被识别。更多关于区块变体选择器的信息请参阅此处

然而,目前不建议使用此方式,这是因为查询循环通过模式和 scope: [ 'block' ] 变体进行设置时,所选模式的所有属性将被使用,但 postTypeinherit 查询属性除外,这可能导致冲突和变体功能异常。

为解决此问题,有两种方法。第一种是添加默认的 innerBlocks,如下所示:

innerBlocks: [
	[
		'core/post-template',
		{},
		[ [ 'core/post-title' ], [ 'core/post-excerpt' ] ],
	],
	[ 'core/query-pagination' ],
	[ 'core/query-no-results' ],
],

通过在变体中包含 innerBlocks,您实际上跳过了查询循环区块通过建议模式进行设置的阶段,区块将以这些内部区块作为初始内容插入。

另一种方法是为您的变体注册特定模式,这些模式将在设置过程中被推荐,并替换区块的流程。

查询循环区块会判断自身是否有活跃变体,以及该变体是否有特定模式可用。如果有,这些模式将是唯一推荐给用户的模式,不会包含原始查询循环区块的默认模式。否则,如果没有此类模式,将推荐默认模式。

若要将模式与查询循环变体“关联”,您应在模式的 blockTypes 属性中添加以查询循环名称前缀的变体名称(例如 core/query/$variation_name)。有关注册模式的更多详情,请参阅此处

如果您未在变体中提供 innerBlocks,还有一种方法可以在用户于设置阶段选择 Start blank 时推荐“关联”变体。这与“关联”模式的处理方式类似,通过检查查询循环是否有活跃变体以及是否有任何关联变体可推荐。

若要将一个变体与另一个查询循环变体关联,我们需要将 scope 属性定义为 ['block'],并将 namespace 属性定义为一个数组。该数组应包含希望关联的变体的名称(name 属性)。

例如,如果我们有一个名称为 products 的查询循环变体暴露于插入器中(scope: ['inserter']),我们可以通过将其 namespace 属性设置为 ['products'] 来关联一个作用域为 block 的变体。如果用户在点击 Start blank 后选择此变体,namespace 属性将被主要的插入器变体覆盖。

3. 让 Gutenberg 识别您的变体

在实现此变体后,您可能意识到一个小问题:虽然用户在插入时对此无感知,但 Gutenberg 仍会将变体识别为查询循环区块的核心功能,因此在插入后,例如在编辑器的树形视图中,它将显示为查询循环区块。

我们需要一种方式告诉编辑器,此区块实际上是您的特定变体。这正是 isActive 属性的用途:它是一种基于区块属性判断特定变体是否活跃的方法。您可以像这样使用它:

{
	/** ...变体属性 */
	isActive: ( { namespace, query } ) => {
		return (
			namespace === MY_VARIATION_NAME
			&& query.postType === 'book'
		);
	},
}

您可能倾向于仅比较 postType,以便当 postType 匹配 bookGutenberg 会将区块识别为您的变体。然而,这种方式范围过广,因为其他插件可能也希望基于 book 文章类型发布变体,或者我们可能不希望用户在编辑器设置中手动将类型设置为 book 时每次都识别为变体。

这就是为什么查询循环区块暴露了一个名为 namespace 的特殊属性。它在区块实现中实际上没有任何作用,而是作为一种简单且一致的方式供扩展者识别和限定自己的变体。此外,isActive 还可以接受一个字符串数组,用于比较属性。通常,namespace 就足够了,您可以这样使用:

{
	/** ...变体属性 */
	attributes: {
		/** ...变体属性 */
		namespace: 'my-plugin/books-list',
	},
	isActive: [ 'namespace' ],
}

这样Gutenberg 只有在匹配您的自定义命名空间时才会知道这是您的特定变体!非常方便!