14 KiB
在前端实现自定义查询功能
查询循环区块主要通过文章模板区块运作,该区块接收属性并据此构建查询。查询循环区块的其他一级子元素(如分页区块)也以相同方式工作。它们构建查询后,通过 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' ] 变体进行设置时,所选模式的所有属性将被使用,但 postType 和 inherit 查询属性除外,这可能导致冲突和变体功能异常。
为解决此问题,有两种方法。第一种是添加默认的 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 匹配 book 时,Gutenberg 会将区块识别为您的变体。然而,这种方式范围过广,因为其他插件可能也希望基于 book 文章类型发布变体,或者我们可能不希望用户在编辑器设置中手动将类型设置为 book 时每次都识别为变体。
这就是为什么查询循环区块暴露了一个名为 namespace 的特殊属性。它在区块实现中实际上没有任何作用,而是作为一种简单且一致的方式供扩展者识别和限定自己的变体。此外,isActive 还可以接受一个字符串数组,用于比较属性。通常,namespace 就足够了,您可以这样使用:
{
/** ...变体属性 */
attributes: {
/** ...变体属性 */
namespace: 'my-plugin/books-list',
},
isActive: [ 'namespace' ],
}
这样,Gutenberg 只有在匹配您的自定义命名空间时才会知道这是您的特定变体!非常方便!