### 在前端实现自定义查询功能 查询循环区块主要通过文章模板区块运作,该区块接收属性并据此构建查询。查询循环区块的其他一级子元素(如分页区块)也以相同方式工作。它们构建查询后,通过 [`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 ) ? ( <> { /** 我们的自定义组件 */ } ) : ( ); }; 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 只有在匹配您的自定义命名空间时才会知道这是您的特定变体!非常方便!