社区新闻

使用 Query Loop 块变体构建书评网格

查看官方原文 ↗ 发布于

WordPress 6.1 引入了扩展 Query Loop 块的新方法。这是一个重要的里程碑,因为它以极低的代码成本为插件开发者提供了大量功能。开发者无需构建自定义块来查询文章,只需过滤 WordPress 核心中的现有功能即可。

Query Loop 块是使用区块构建网站的主力。它是显示文章、页面和其他自定义文章类型内容的基础。

在 WordPress 6.1 之前,与 PHP 对应的 WP_Query 相比,它的主要用例仅限于显示以前可能实现的功能子集。这意味着开发者,尤其是在处理自定义文章类型的数据时,通常需要自己构建完整的自定义块。

如今,开发者可以在坚实的基础上进行构建,只需最少的代码即可输出几乎任何类型的内容。6.1 版本的更改允许插件作者跳过区块开发环节,直接扩展内置的 Query Loop 块。

其使用方式的一些例子包括:

  • 按价格元字段显示产品网格。
  • 按位置列出企业的商业目录。
  • 点对点筹款活动的排行榜。
  • 按评分输出书评。

在本教程中,你将学习如何处理列表中的最后一项:列出书评文章。你将从头到尾构建一个 WordPress 插件。结果将是一个类似于下图的 Query Loop 块变体:

WordPress 文章编辑器,显示三篇书评文章,每篇都有特色图片和标题。

这个简单教程中的基本方法也可以应用于更复杂的项目。

要求

除了一些基础的 JavaScript 开发知识和区块开发经验外,你的机器上应具备以下工具:

  • Node/NPM 开发工具
  • WordPress 开发站点
  • 代码编辑器

有关设置这些工具的更多信息,请访问区块编辑器手册中的开发环境指南。

设置内容

在本教程中,假设你有一个客户,他喜欢时不时地写书评,并希望在其网站的不同位置(例如自定义页面)展示最新的书评。该客户有一个名为“书评”的自定义分类,并且已经写了几篇文章。

在你的开发环境中重现此场景。

WordPress 文章管理屏幕,其中已选择“书评”分类。表格中显示了三篇文章。

首先,添加一个新的“书评”分类,并记下分类 ID。稍后你会需要它。然后,创建至少三篇分配给此分类的示例文章,并为每篇文章设置特色图片。

插件设置

在你的 wp-content/plugins 目录中创建一个新插件。将其命名为类似 book-reviews-grid 的名称(确切名称并不特别重要)。现在,按照以下特定结构添加文件:

book-reviews-grid
    /index.php
    /package.json
    /src
        /index.js

你可以将 index.php 更改为你想要的任何名称。它是你的插件的主要 PHP 文件。

PHP 设置

在你的插件主 PHP 文件中,添加包含一些基本信息的插件头部:

<?php
/**
 * Plugin Name:       Book Reviews Grid
 * Version:           1.0.0
 * Requires at least: 6.1
 * Requires PHP:      7.4
 */

// Additional code goes here…

这将是本教程中你唯一需要的 PHP 文件,所有 PHP 代码都将放入其中。

构建过程设置

首先,打开你的 package.json 文件并添加 start 脚本。这将用于构建过程。如果需要,你可以添加其他字段,例如 namedescription

{
    "scripts": {
        "start": "wp-scripts start"
    }
}

本教程需要 @wordpress/scripts 包,可以通过命令行安装:

npm install @wordpress/scripts --save-dev

设置好所有内容后,在命令行程序中输入以下内容:

npm run start

除了必需的 start 命令外,你还可以通过 @wordpress/scripts 包找到所有可用的脚本,并根据需要将它们添加到你的 package.json 中。

构建一个简单的 Query Loop 变体

注册一个简单的 Query Loop 变体(没有任何自定义查询变量集成)的过程只需要几十行代码。你必须导入 registerBlockVariation 并使用它来注册变体。

JavaScript:构建变体

在你的 src/index.js 文件顶部,添加以下代码行:

import { registerBlockVariation } from '@wordpress/blocks';

现在,你需要两条信息。首先,为变体决定一个唯一的名称。暂时使用 book-reviews。需要的第二条数据是你之前在本教程中创建的“书评”分类的 ID。

将这两个值分配给常量,如下面的代码片段所示:

const VARIATION_NAME     = 'book-reviews';
const REVIEW_CATEGORY_ID = 8; // 分配自定义分类 ID。

现在,是时候注册变体了。首先,添加一些基本属性,例如名称、标题等,如下面的代码块所示:

registerBlockVariation( 'core/query', {
    name: VARIATION_NAME,
    title: 'Book Reviews',
    icon: 'book',
    description: 'Displays a list of book reviews.',
    isActive: [ 'namespace' ],
    // 其他变体选项...
} );

core/query 块注册变体时,需要设置两个必要的选项:

  • name 属性应与你的唯一变体名称匹配。
  • isActive 属性应是一个包含 namespace 属性的数组(你将在下一步中定义此属性)。

从这一点来看,变体大部分是可定制的,但让我们一步一步来,为变体添加新选项。接下来要构建的是变体的属性。属性可以匹配 Query Loop 块接受的任何属性。

一个额外的必需属性是 namespace。它必须与变体名称匹配,以便 WordPress 能够检查它是否是活动变体。

在本教程中,变体显示“书评”分类中最新六篇文章。它还具有三列网格的宽布局。请随意根据你的喜好自定义选项。

registerBlockVariation( 'core/query', {
    // ...之前的变体选项。
    attributes: {
        namespace: VARIATION_NAME,
        query: {
            postType: 'post',
            perPage: 6,
            offset: 0,
            taxQuery: {
                category: [ REVIEW_CATEGORY_ID ]
            }
        },
        align: 'wide',
        displayLayout: {
            type: 'flex',
            columns: 3
        }
    },
    // 其他变体选项...
} );

开发者还可以通过设置 allowedControls 数组来选择显示哪些默认的 WordPress 控件(默认情况下显示所有控件)。这些在界面中显示为区块选项。有关控件及其定义的完整列表,请访问区块编辑器手册中的允许的控件部分

以下示例添加了 orderauthor 控件:

registerBlockVariation( 'core/query', {
    // ...之前的变体选项。
    allowedControls: [
        'order',
        'author'
    ],
    // 其他变体选项...
} );

最后,你应该为变体添加一些内部块。第一个顶级块应始终是 core/post-template。以下代码片段使用了核心的“文章特色图片”和“文章标题”块,没有进行自定义,但你可以随意添加其他块并为每个块设置默认选项。

registerBlockVariation( 'core/query', {
    // ...之前的变体选项。
    innerBlocks: [
        [
            'core/post-template',
            {},
            [
                [ 'core/post-featured-image' ],
                [ 'core/post-title' ]
            ],
        ]
    ]
} );

有关可用选项的完整概述,请访问以下资源:

PHP:加载 JavaScript

处理完基础的 JavaScript 后,现在你必须加载 JavaScript 文件本身。构建过程将生成两个文件:

  • build/index.js: 要加载的 JavaScript 文件。
  • build/index.asset.php: 脚本的依赖项数组和版本号。

以下代码片段应添加到 index.php 中。如果生成的资源文件存在,则获取其数据并加载编辑器中的脚本:

add_action( 'enqueue_block_editor_assets', 'myplugin_assets' );

function myplugin_assets() {

    // 获取插件目录和 URL 路径。
    $path = untrailingslashit( __DIR__ );
    $url  = untrailingslashit( plugins_url( '', __FILE__ ) );

    // 获取自动生成的资源文件。
    $asset_file = "{$path}/build/index.asset.php";

    // 如果资源文件存在,获取其数据并加载脚本。
    if ( file_exists( $asset_file ) ) {
        $asset = include $asset_file;

        wp_enqueue_script(
            'book-reviews-variation',
            "{$url}/build/index.js",
            $asset['dependencies'],
            $asset['version'],
            true
        );
    }
}

有了这段代码,你应该能够通过区块插入器或区块编辑器中的斜杠命令(例如 /book reviews)添加“书评”变体:

WordPress 文章编辑器,区块插入器打开。它显示着“书评”区块变体。

如果你的变体不需要对查询的文章进行超出核心 Query Loop 块开箱即用功能的自定义,你可以在此处停止本教程。

将文章元数据集成到变体中

现在,让我们深入探讨一个基于现有代码构建的稍微高级的场景。你将构建一个控件,供用户根据文章元键和值对来显示书评。

实现此功能的关键在于 WordPress 提供的过滤器钩子。一旦你学会了如何使用这些钩子,就可以将它们扩展到自定义文章类型和其他实际项目中。

设置文章元数据

你需要一个值为 15 之间的 rating 文章元键,并将其附加到“书评”分类中的一篇或多篇文章上。最简单的方法是使用文章编辑屏幕上的自定义字段面板。

注意:如果看不到“自定义字段”面板,可以在编辑器的选项(⋮ 图标)> 偏好设置 > 面板菜单中启用它

返回你的每篇书评文章,并添加一个评分值,如下图所示:

WordPress 文章编辑器,屏幕底部显示“自定义字段”面板。面板显示一个值为 5 的 rating 字段。

在实际项目中,你可能会希望为最终用户构建适当的表单字段,以便轻松选择星级评分。然而,这超出了本教程的范围。

JavaScript:添加区块变体控件

要为自定义 Query Loop 变体添加额外的控件,你需要在脚本中导入一些额外的模块。将以下代码添加到 src/index.js 文件的顶部。在构建其余功能时,你将使用这些模块。

// ...之前的导入。
import { addFilter } from '@wordpress/hooks';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, SelectControl } from '@wordpress/components';

接下来,构建一个快速的辅助函数,用于根据区块的属性判断它是否与你的变体匹配。稍后你将在代码中使用它。大部分工作已经完成,因为你之前设置了变体名称常量和用于检查的命名空间。

const isBookReviewsVariation = ( props ) => {
    const {
        attributes: { namespace }
    } = props;

    return namespace && namespace === VARIATION_NAME;
};

现在,是时候构建一个用于显示自定义区块面板部分的组件了。稍后你可以在此处添加多个字段。目前,它将包含一个用于选择星级评分的表单字段。

以下代码使用 SelectControl 组件创建每个可用评分的下拉选择。这同样可以是一个单选列表、按钮组或完全自定义的 React 组件。这取决于你。

这段代码的关键部分是将星级评分值保存到 props.attributes.query.starRating。稍后你将需要它来修改文章查询。

const BookReviewControls = ( { props: {
    attributes,
    setAttributes
} } ) => {
    const { query } = attributes;

    return (
        <PanelBody title="Book Review">
            <SelectControl
                label="Rating"
                value={ query.starRating }
                options={ [
                    { value: '', label: '' },
                    { value: 1,  label: "1 Star" },
                    { value: 2,  label: "2 Stars" },
                    { value: 3,  label: "3 Stars" },
                    { value: 4,  label: "4 Stars" },
                    { value: 5,  label: "5 Stars" }
                ] }
                onChange={ ( value ) => {
                    setAttributes( {
                        query: {
                            ...query,
                            starRating: value
                        }
                    } );
                } }
            />
        </PanelBody>
    );
};

构建好自定义面板部分和控件后,你必须过滤 Query Loop (core/query) 块以添加自定义控件。这就是之前 isBookReviewsVariation 辅助函数的用武之地。你将传递区块的属性给它,以判断它是否是自定义变体。如果匹配,则添加你的控件。

export const withBookReviewControls = ( BlockEdit ) => ( props ) => {

    return isBookReviewsVariation( props ) ? (
        <>
            <BlockEdit {...props} />
            <InspectorControls>
                <BookReviewControls props={props} />
            </InspectorControls>
        </>
    ) : (
        <BlockEdit {...props} />
    );
};

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

此时,当你的变体被使用时,你应该能看到一个标题为“Book Review”的部分,其中包含一个“Rating”选择下拉菜单,如下图所示:

WordPress 文章编辑器,显示三篇文章的网格,每篇都有特色图片和标题。侧边栏中显示了一个星级评分下拉菜单。

此时选择评分不应改变查询的文章。还需要添加几个过滤器才能使其工作。

PHP:过滤查询的文章

你必须添加两个 PHP 过滤器,以便在编辑器和前端都能工作。然后,你将拥有一个功能齐全的、集成了文章元数据的 Query Loop 变体。

第一个过滤器将挂在 rest_{$post_type}_query 钩子上。因为你正在使用“post”文章类型构建此功能,所以钩子名称变为 rest_post_query

此过滤器将针对该类型的每个查询运行,因此在更改之前需要检查自定义查询参数 (starRating)。你可以通过回调函数的 $request 参数来检查,该参数提供了 WP_REST_Request的实例。使用其 get_param() 方法来检查自定义查询参数。

如果设置了 starRating 值,你只需要将元键和值作为查询参数传递回去。为此,将以下代码添加到你的插件主 PHP 文件中:

add_filter( 'rest_post_query', 'myplugin_rest_book_reviews', 10, 2 );

function myplugin_rest_book_reviews( $args, $request ) {

    $rating = $request->get_param( 'starRating' );

    if ( $rating ) {
        $args['meta_key'] = 'rating';
        $args['meta_value'] = absint( $rating );
    }

    return $args;
}

现在,在编辑器中测试你之前构建的评分控件。如下图所示,只有具有所选评分的文章被查询:

WordPress 文章编辑器,显示网格中的两篇文章,每篇都有特色图片和标题。侧边栏中选择了 5 星评分。

虽然这在编辑器中有效,但你还需要使用 pre_render_block 钩子在区块在前端渲染时运行一些自定义代码。然后,你需要使用匿名函数在回调内部嵌套第二个过滤器到 query_loop_block_query_vars 钩子上。这样做的原因是你需要访问已解析的区块属性。

如果听起来有点复杂,确实如此。理想情况下,未来会有一种稍微不那么复杂的方法来实现这一点。

add_filter( 'pre_render_block', 'myplugin_pre_render_block', 10, 2 );

function myplugin_pre_render_block( $pre_render, $parsed_block ) {

    // 判断这是否是自定义区块变体。
    if ( 'book-reviews' === $parsed_block['attrs']['namespace'] ) {

        add_filter(
            'query_loop_block_query_vars',
            function( $query, $block ) use ( $parsed_block ) {

                // 如果查询了评分,则添加评分元键/值对。
                if ( $parsed_block['attrs']['query']['starRating'] ) {
                    $query['meta_key'] = 'rating';
                    $query['meta_value'] = absint( $parsed_block['attrs']['query']['starRating'] );
                }

                return $query;
            },
            10,
            2
        );
    }

    return $pre_render;
}

现在,你应该在网站前端看到与编辑器中相同的查询文章。

有了这个基础,你可以将其扩展到其他项目。本质上,你可以使用几个过滤器和自定义控件构建任何你需要的查询类型。虽然本教程看起来有点长,但总共包含不到 200 行代码,与构建功能齐全的区块相比,这是一个重大的胜利。