跳到正文
原文
WordPress 开发者博客· Justin Tadlock·· 2026-06-19编辑评分47

在 block theme 中动态加载模板部件

Dynamically loading template parts in block themes

编辑导读

WordPress 可通过 `render_block_data` 过滤器修改 Template Part 区块的 `slug`,按页面或文章分类动态加载对应的模板部件。该方法可替换侧边栏、页眉、页脚、横幅和评论区域等部件;示例仅作用于 `post` 的单独文章页面,并在未找到分类专属模板时回退到 `sidebar-post.html`。

我和我的团队一直在开发一个与音乐相关的 WordPress 项目。为了这个项目,我需要一种根据网站访问者正在查看的页面来显示不同侧边栏的方法。这是区块主题作者时不时会遇到的问题。

在经典主题中,这一直感觉很简单:只需在模板本身中放入加载不同模板部分的逻辑。虽然将逻辑代码放在模板中存在架构设计问题——这与本文基本无关——但它确实能奏效。你原本可以调用 get_sidebar()、get_header()、get_footer() 或 get_template_part(),并向这些函数的参数传递任意变量,以获得所需结果。

对于区块主题,你无法在基于 HTML 的模板中这样做。即使是在通过 PHP 注册的模式中,在数据被处理之前你也无法访问大多数数据(模式注册于 init),因此无法按上下文加载模板部分。

区块主题一直采用的解决方案是使用多个顶层模板。为这个分类创建一个模板,为那个标签创建另一个模板,再为其他内容创建第三个模板。它确实有效,但这也意味着需要维护越来越多的几乎完全相同的模板,而它们之间的唯一区别只是加载了哪个模板部分。

还有一种更好的方法。WordPress 允许你在区块数据渲染之前拦截它,并动态替换模板部分的 slug——将默认模板部分替换为特定上下文对应的模板部分。虽然我的使用场景是侧边栏,但同样的技术适用于任何模板部分:页眉、页脚、横幅、评论区,以及主题需要的任何内容。

本教程将介绍如何构建这个系统,先从核心过滤器讲起,再扩展到更广泛的用例。

目录

  1. render_block_data 过滤器钩子
  2. 构建基于分类的侧边栏
  3. 设置模板部分

render_block_data 过滤器钩子

render_block_data 过滤器钩子会在任何区块渲染之前触发。它会以数组形式提供已解析的区块数据——包括区块名称及其属性——并允许你在 WordPress 对其执行任何操作之前修改这些数据。

对于模板部分区块(core/template-part),最有用的属性是 slug,它告诉 WordPress 要加载哪个模板部分文件。更改该 slug,WordPress 就会加载完全不同的模板部分。

下面是过滤器签名:

add_filter( 'render_block_data', 'themeslug_render_template_part_data' );

function themeslug_render_template_part_data( array $parsed_block ): array 
{
	// Your logic here.
	return $parsed_block;
}

关键规则:无论是否修改过数据,始终返回 $parsed_block。此过滤器会为页面上的每个区块运行,因此在任何实现中,你要做的第一件事就是对于不关心的区块提前退出。

构建基于分类的侧边栏

让我们通过一个具体示例来讲解。对于这个音乐项目,它只需要在单篇文章视图中显示一个名为 sidebar-post.html 的侧边栏(位于主题的 /parts 文件夹中),因此这是一个范围较窄的用例。

在我的主题 templates/single-post.html 文件中,侧边栏模板通过以下标记调用:

<!-- wp:template-part {"slug":"sidebar-post"} /-->

下面是正常浏览网站时模板部分的样子:

Single blog post view from a music article. It has a title and image that spans in a wide width across the screen, sitting above two columns. Content on the left and a sidebar on the right (showing the latest posts and a subscription form).

如上所示,它会显示最新文章和订阅按钮。但对于这个项目,我需要让单篇文章侧边栏在另外两种场景中显示不同的数据:

  • sidebar-post-artist-spotlight.html:Artist Spotlight 分类
  • sidebar-post-album-reviews.html:Album Reviews 分类

下面是完整的过滤器:

add_filter( 'render_block_data', 'themeslug_render_template_part_data' );

function themeslug_render_template_part_data( array $parsed_block ): array 
{
	// Only target the Template Part block with a `sidebar-post` slug and
	// when viewing a single post.
	if (
		( $parsed_block['blockName'] ?? '' ) !== 'core/template-part'
		|| ( $parsed_block['attrs']['slug'] ?? '' ) !== 'sidebar-post'
		|| ! is_singular( 'post' )
	) {
		return $parsed_block;
	}

	$post = get_queried_object();

	if ( ! $post instanceof WP_Post ) {
		return $parsed_block;
	}

	// Get the directory where template parts live.
	$parts_dir = get_block_theme_folders()['wp_template_part'];

	// Loop through the post's categories and look for a matching template part.
	foreach ( get_the_category( $post->ID ) as $category ) {
		$slug = "sidebar-post-{$category->slug}";

		if ( locate_template( "{$parts_dir}/{$slug}.html" ) ) {
			$parsed_block['attrs']['slug'] = $slug;
			return $parsed_block;
		}
	}

	// No category-specific part found; return the original unchanged.
	return $parsed_block;
}

这段代码中有几点值得特别说明。

顶部的提前返回检查很重要。此过滤器会为每个页面上的每个区块触发,因此对于不符合条件的内容,你要尽快退出:

  • 区块名称是 core/template-part。
  • slug 名称是 sidebar-post。
  • 用户当前正在查看单篇文章。
  • 当前查询到的对象是 WP_Post。

对 get_block_theme_folders()['wp_template_part'] 的调用会返回主题用于模板部件的目录——默认是 parts。

请注意,如果找不到特定类别的模板部件,该函数会返回 $parsed_block,而不对其进行修改。这意味着 WordPress 会加载原始的 sidebar-post.html——即模板本身中定义的那个。你无需任何额外逻辑,就能获得回退行为。

遍历 get_the_category() 的循环也会自动处理优先级。如果一篇文章属于多个类别,则按术语顺序返回这些类别。第一个匹配的模板部件文件优先。如果需要不同的优先级顺序,可以在迭代之前按任意方式对类别排序。

设置模板部件

要让此系统正常运行,你需要将模板部件文件放在正确的位置。至少需要默认的 parts/sidebar-post.html。

然后根据需要添加特定类别的版本:

  • parts/sidebar-post-author-spotlight.html
  • parts/sidebar-post-album-reviews.html

只创建实际需要的版本。如果 sidebar-post-author-spotlight.html 不存在,循环会跳过它并继续检查。这正是该系统具有韧性的原因:你不必为每个可能的类别都创建模板部件,只需为希望呈现不同内容的类别创建即可。

我的 sidebar-post.html 块标记如下所示,但你的标记应适合你的主题:

<!-- wp:group {"style":{"layout":{"selfStretch":"fit","flexSize":null}},"layout":{"type":"flex","orientation":"vertical","justifyContent":"stretch"}} -->
<div class="wp-block-group">

	<!-- wp:group {"className":"is-style-section-3","style":{"spacing":{"blockGap":"var:preset|spacing|40"}},"layout":{"type":"flex","orientation":"vertical"}} -->
	<div class="wp-block-group is-style-section-3">

		<!-- wp:heading {"level":3,"className":"is-style-text-widget-heading"} -->
		<h3 class="wp-block-heading is-style-text-widget-heading">Latest Posts</h3>
		<!-- /wp:heading -->

		<!-- wp:query {"queryId":3,"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"ignore","inherit":false,"taxQuery":null,"parents":[],"format":[]},"className":"is-style-query-numbered"} -->
		<div class="wp-block-query is-style-query-numbered">

			<!-- wp:post-template {"style":{"spacing":{"blockGap":"var:preset|spacing|40"}},"layout":{"type":"default"}} -->
				
				<!-- wp:group {"style":{"spacing":{"blockGap":"var:preset|spacing|20"}},"layout":{"type":"constrained"}} -->
				<div class="wp-block-group">
					
					<!-- wp:post-title {"level":3,"isLink":true} /-->

					<!-- wp:group {"className":"is-style-meta","style":{"spacing":{"blockGap":"var:preset|spacing|10"}},"fontSize":"3-xs","layout":{"type":"flex","flexWrap":"nowrap"}} -->
					<div class="wp-block-group is-style-meta has-3-xs-font-size">
						<!-- wp:post-date {"format":"human-diff","metadata":{"bindings":{"datetime":{"source":"core/post-data","args":{"field":"date"}}}}} /-->
					</div>
					<!-- /wp:group -->
				
				</div>
				<!-- /wp:group -->
			
			<!-- /wp:post-template -->
		
		</div>
		<!-- /wp:query -->
	
	</div>
	<!-- /wp:group -->

	<!-- wp:group {"style":{"spacing":{"blockGap":"var:preset|spacing|30"}},"layout":{"type":"flex","orientation":"vertical","justifyContent":"stretch"}} -->
	<div class="wp-block-group">
		
		<!-- wp:paragraph {"className":"is-style-default","style":{"typography":{"textAlign":"center"}},"fontSize":"3-xs","fontFamily":"mono"} -->
		<p class="has-text-align-center is-style-default has-mono-font-family has-3-xs-font-size">Join 12K Subscribers</p>
		<!-- /wp:paragraph -->

		<!-- wp:buttons -->
		<div class="wp-block-buttons">
			<!-- wp:button {"width":100} -->
			<div class="wp-block-button has-custom-width wp-block-button__width-100"><a class="wp-block-button__link wp-element-button">Subscribe</a></div>
			<!-- /wp:button -->
		</div>
		<!-- /wp:buttons -->
	
	</div>
	<!-- /wp:group -->
	
</div>
<!-- /wp:group -->

sidebar-post-artist-spotlight.html 和 sidebar-post-album-reviews.html 可以完全不同,以展示你想呈现的任何内容。

以下是专辑评论侧边栏实际运行时的截图:

Single blog post view from a music article about an album. It has a title and image that spans in a wide width across the screen, sitting above two columns. Content on the left and a sidebar on the right (showing an album spotlight and a subscription form).

在这种情况下,sidebar-post-album-reviews.html 部件与正在评论的音乐专辑相关联。这是通过自定义 Query 块变体和文章元数据实现的。要了解有关该技术的更多信息,请阅读《使用 Query Loop 块变体构建图书评论网格》。

关键在于,这不需要为每个类别都创建自定义单篇文章模板。我只需要为页面中需要更改的部分添加模板部件,从而减少代码重复。


render_block_data 过滤器在区块主题领域受到的关注远远不够。它是一种简洁、资源占用少的方式,可以让你的主题布局真正具备动态性,而无需重复模板。一旦掌握这种模式,你会发现自己在各种场景中都会使用它——不只是侧边栏。

不妨试试,看看能做出什么。

感谢 @bph、@areziaal、@welcher 和 @juanmaguitar 对本文提供反馈。

来源:WordPress 开发者博客 · https://developer.wordpress.org/news/2026/06/dynamically-loading-template-parts-in-block-themes/

部分链接为推广/关联链接。