查询循环块(Query Loop block)是一个强大的工具,允许用户遍历确定的文章列表,并显示一组继承列表中每篇文章上下文的块。例如,它可以设置为遍历某个类别的所有文章,并为每篇文章显示其特色图片。当然,还有更多功能!

但正是因为查询循环块如此强大且允许高度自定义,它也可能令人望而生畏。大多数用户并不希望看到查询循环块的完整功能,因为大多数用户不熟悉“查询”的概念及其相关技术术语。相反,大多数用户可能会更喜欢该块的预设版本,其设置更少,命名更清晰。默认提供的文章列表变体就是这种做法的一个好例子:用户将使用查询循环块而无需了解其技术细节,并且也更有可能发现和了解该块的目的。

同样,许多扩展程序可能需要一种方法来呈现该块的定制版本,带有自己的预设、额外设置以及与其用例无关的自定义选项(通常例如为其自定义文章类型)。查询循环块提供了创建此类变体的非常强大的方法。

使用变体扩展块

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

借助块变体 API,您可以提供对您的用例最有意义的默认设置。

为了使查询循环变体正常工作,我们需要:
– 为 core/query 块注册具有某些默认值的块变体
– 定义该块变体的布局
– 在 isActive 块变体属性中使用 namespace 属性

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

1. 提供合理的默认值

您的第一步是创建一个变体,其设置方式将提供一个块变体,默认显示书籍列表而不是博客文章。完整的变体代码大致如下:

const MY_VARIATION_NAME = 'my-plugin/books-list';

registerBlockVariation( 'core/query', {
    name: MY_VARIATION_NAME,
    title: 'Books List',
    description: 'Displays a list of books',
    isActive: ( { namespace, query } ) => {
        return (
            namespace === MY_VARIATION_NAME
            && query.postType === 'book'
        );
    },
    icon: /** An SVG icon can go here*/,
    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: {
            /** ...more query settings if needed */
            postType: 'book',
        },
    },
} );

通过这种方式,用户无需从下拉菜单中选择自定义 postType,而是直接看到正确的配置。

变体的 query 会替换块的默认 query 对象,而不是与其合并,因此请包含您的变体依赖的所有属性,而不仅仅是您更改的那些。始终设置 postType:如果缺少该属性,块将在编辑器和前端都查询文章。

然而,您可能想知道用户如何找到并插入此变体?好问题!要启用此功能,您应该添加:

{
    /** ...variation properties */
    scope: [ 'inserter' ],
}

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

{
    /** ...variation properties */
    title: 'Books List',
    description: 'Displays a list of books',
    icon: /* Your svg icon here */,
}

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

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

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 属性)。

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

3. 让 Gutenberg 识别您的变体

在实现此变体后,您可能已经意识到一个轻微的问题:虽然对用户来说是透明的,因为他们正在插入它,但 Gutenberg 仍然会在其核心将其识别为查询循环块,因此,在其插入后,它将在编辑器的树视图中显示为查询循环块,例如。

我们需要一种方法来告诉编辑器该块确实是您的特定变体。这就是 isActive 属性所用于的:它是一种基于块的属性确定某个变体是否活动的方法。您可以像这样使用它:

{
    /** ...variation properties */
    isActive: ( { namespace, query } ) => {
        return (
            namespace === MY_VARIATION_NAME
            && query.postType === 'book'
        );
    },
}

你可能会被诱惑,仅比较 postType,以便 Gutenberg 在 postType 匹配 book 时始终将块识别为你的变体。但这撒网太宽了,因为其他插件可能也想基于 book 文章类型发布变体,或者我们可能只是不希望每次用户通过编辑器设置手动将类型设置为 book 时就识别该变体。

这就是为什么查询循环块(Query Loop block)暴露了一个名为 namespace 的特殊属性。它在块的实现内部实际上什么都不做,而是作为一种简单且一致的方式供扩展程序识别和限定其自身的变体。此外,isActive 也仅接受一个包含要比较属性的字符串数组。通常,namespace 就足够了,因此你可以这样使用它:

{
    /** ...variation properties */
    attributes: {
        /** ...variation attributes */
        namespace: 'my-plugin/books-list',
    },
    isActive: [ 'namespace' ],
}

就像这样,Gutenberg 只有在匹配你的自定义命名空间的情况下才会知道这是你特定的变体!多么方便!

扩展查询

即使有了所有这些,你的自定义文章类型可能具有独特的需求:它可能支持某些你想要过滤和查询的自定义属性,或者某些其他查询参数可能无关紧要甚至完全不受支持!我们构建查询循环块时已考虑到此类用例,让我们看看如何解决这个问题。

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

假设你在书籍中完全不使用 sticky 属性,那么这对块的自定义来说将完全不相关。为了避免让用户对设置的作用感到困惑,并仅向他们展示清晰的 UX,我们希望此控件不可用。此外,假设你完全不使用 author 字段(该字段通常表示将文章添加到数据库的人),而是使用自定义的 bookAuthor 字段。因此,保留 author 过滤器不仅令人困惑,而且会直接“破坏”你的查询。

出于这个原因,查询循环块变体支持一个名为 allowedControls 的属性,它接受我们要在检查器侧边栏上显示的控件的键数组。默认情况下,我们接受所有控件,但一旦我们为这个属性提供数组,我们就只想指定对我们相关的控件!

从 Gutenberg 版本 14.2 开始,以下控件可用:

  • inherit – 显示允许查询直接从模板继承的切换开关。
  • postType – 显示可用文章类型的下拉菜单。
  • order – 显示用于选择查询顺序的下拉菜单。
  • sticky – 显示用于选择如何处理置顶文章的下拉菜单。
  • taxQuery – 显示当前选定文章类型的可用分类法过滤器,包括每个分类法的包含和排除控件。
  • author – 显示一个输入字段,用于按作者过滤查询。
  • search – 显示一个输入字段,用于按关键字过滤查询。
  • format – 显示一个输入字段,用于按 格式 数组/集合过滤查询。
  • parents – 显示一个输入字段,用于使用父实体过滤查询。

在我们的案例中,该属性将如下所示:

{
    /** ...variation properties */
    allowedControls: [ 'inherit', 'order', 'taxQuery', 'search' ],
}

如果你想隐藏上述所有可用控件,可以将空数组设置为 allowedControls 的值。

请注意,我们也禁用了 postType 控件。当用户选择我们的变体时,为什么要向他们显示一个令人困惑的下拉菜单来更改文章类型?除此之外,它可能会破坏块,因为我们可以实现自定义控件,正如我们稍后将看到的。

理解 taxQuery 结构

taxQuery 属性支持分类法术语的包含和排除。其结构如下:

{
    query: {
        taxQuery: {
            include: {
                category: [1, 2, 3], // Include posts with these category IDs.
                post_tag: [10, 20] // Include posts with these tag IDs.
            },
            exclude: {
                category: [5, 6], // Exclude posts with these category IDs.
                post_tag: [15] // Exclude posts with these tag IDs.
            }
        }
    }
}

当你在变体中使用 taxQuery 控件时,用户将为每个适用的分类法看到“[Taxonomy]”(包含)和“Exclude: [Taxonomy]”控件。包含和排除在 UI 中是互斥的——在一个中选择的术语不会出现在另一个的建议中。

添加额外控件

因为我们的插件使用需要查询的自定义属性,我们希望添加自己的控件以允许用户选择这些控件,而不是我们刚刚从核心检查器控件中禁用的那些。我们可以通过 React HOC 钩入一个 块过滤器 来实现,如下所示:

import { InspectorControls } from '@wordpress/block-editor';

export const withBookQueryControls = ( BlockEdit ) => ( props ) => {
    // We only want to add these controls if it is our variation,
    // so here we can implement a custom logic to check for that, similar
    // to the `isActive` function described above.
    // The following assumes that you wrote a custom `isMyBooksVariation`
    // function to handle that.
    return isMyBooksVariation( props ) ? (
        <>
            <BlockEdit key="edit" { ...props } />
            <InspectorControls>
                <BookAuthorSelector /> { /** Our custom component */ }
            </InspectorControls>
        </>
    ) : (
        <BlockEdit key="edit" { ...props } />
    );
};

addFilter( 'editor.BlockEdit', 'core/query', withBookQueryControls );

当然,你将负责实现控件的逻辑(你可能想查看 @wordpress/components 以使你的控件无缝融入 Gutenberg UI)。你在块的属性中的 query 对象内分配的任意额外参数都可以用于根据你的需求创建自定义查询,只需付出一点额外的努力。

目前,你可能必须实现略有不同的路径,以便在前端(即最终用户侧)和编辑器侧显示正确的预览时使查询正常工作。

{
    /** ...variation properties */
    attributes: {
        /** ...variation attributes */
        query: {
            /** ...more query settings if needed */
            postType: 'book',
            /** Our custom query parameter */
            bookAuthor: 'J. R. R. Tolkien'
        }
    }
}

使你的自定义查询在前端侧工作

查询循环块主要通过接收属性并从那里构建查询的 Post Template 块运行。查询循环块的其他一等子块(如分页块)以相同的方式运行。它们构建自己的查询,然后通过过滤器 query_loop_block_query_vars 暴露结果。

你可以钩入该过滤器并相应地修改你的查询。只需确保你至少检查只将过滤器应用于你的变体,以免对其他查询循环块产生副作用!

if( 'my-plugin/books-list' === $block[ 'attrs' ][ 'namespace' ] ) {
    add_filter(
        'query_loop_block_query_vars',
        function( $query ) {
            /** You can read your block custom query parameters here and build your 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' );
        /** ...您的自定义查询逻辑 */
    }
);

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