EP199-200. “PlaceholderBlock 类:纯 PHP 驱动、无需构建的占位 Block”
🔒 登录后可标记已读📌 说明:EP199 的 transcript 在「Well, let's actually begin in our functional PHP file and I'll explain why.」这句话结束,EP200 开头是同一句话的重复接续——两者是同一段实操内容被切成两个文件,合并成一篇笔记。
做首页「近期活动 + 最新博文」这个两栏区域,先讨论清楚该不该用 WordPress 核心 Block(columns + query-loop)——结论是不该:视觉上要「背景色撑满全宽、内容区域限定最大宽度」这种版式,核心 columns Block 做不到(除非牺牲对 WordPress 自带响应式断点的控制权);查询逻辑上要「按事件日期(自定义 meta 字段,不是发布日期)筛选未来事件并正序排列」,query-loop 这种简化版查询完全无法表达。更进一步的判断是:这块内容本身不需要任何可编辑的自定义项(不像 Banner 那样需要用户决定标题/按钮/颜色),所以没必要给它配一整套 JS 编辑体验——只需要在编辑器里显示一个占位提示、告诉用户「这里有内容,可以拖到任意页面」,真正的渲染完全交给 PHP。为此新建一个比 JSXBlock 更简化的 PlaceholderBlock 类:不经过 @wordpress/scripts 构建流程(不需要 JSX,直接用 wp.element.createElement 写最朴素的 JS)、PHP 渲染回调强制开启(不像 JSXBlock 是可选项)。
涉及文件
wp-content/themes/fictional-university-block-theme/functions.php(修改,新增PlaceholderBlock类)wp-content/themes/fictional-university-block-theme/our-blocks/eventsandblogs.js(新建)wp-content/themes/fictional-university-block-theme/our-blocks/eventsandblogs.php(新建,从传统主题front-page.php搬运既有查询代码)wp-content/themes/fictional-university-block-theme/css/shame.css(修改,占位样式,快速原型用)
代码实现
functions.php:新建 PlaceholderBlock 类(比 JSXBlock 更简化,脚本直接从 our-blocks/ 加载,不走 build/,PHP 渲染强制开启):
class PlaceholderBlock {
function __construct($name) {
$this->name = $name;
add_action('init', [$this, 'onInit']);
}
function ourRenderCallback($attributes, $content) {
ob_start();
require get_theme_file_path("/our-blocks/{$this->name}.php");
return ob_get_clean();
}
function onInit() {
wp_register_script($this->name, get_stylesheet_directory_uri() . "/our-blocks/{$this->name}.js", array('wp-blocks', 'wp-editor'));
register_block_type("ourblocktheme/{$this->name}", array(
'editor_script' => $this->name,
'render_callback' => [$this, 'ourRenderCallback']
));
}
}
new PlaceholderBlock("eventsandblogs");
our-blocks/eventsandblogs.js(新建,不经过 JSX/构建流程,直接用浏览器全局 wp.blocks/wp.element):
wp.blocks.registerBlockType("ourblocktheme/eventsandblogs", {
title: "Events and Blogs",
edit: function () {
return wp.element.createElement("div", { className: "our-placeholder-block" }, "Events and Blogs Placeholder")
},
save: function () {
return null
}
})
css/shame.css:占位 Block 的通用视觉样式(shame.css 是这套 CSS 组织习惯里专门放「快速修复、不追求分类整洁」内容的文件):
.our-placeholder-block {
padding: 20px;
text-align: center;
font-size: 1.65rem;
background: linear-gradient(180deg, #ddd, #eee);
}
our-blocks/eventsandblogs.php(新建,从传统主题 front-page.php 原样搬运既有查询代码):
<div class="full-width-split group">
<div class="full-width-split__one">
<div class="full-width-split__inner">
<h2 class="headline headline--small-plus t-center">Upcoming Events</h2>
<?php
$today = date('Ymd');
$homepageEvents = new WP_Query(array(
'posts_per_page' => 2,
'post_type' => 'event',
'meta_key' => 'event_date',
'orderby' => 'meta_value_num',
'order' => 'ASC',
'meta_query' => array(
array(
'key' => 'event_date',
'compare' => '>=',
'value' => $today,
'type' => 'numeric'
)
)
));
while($homepageEvents->have_posts()) {
$homepageEvents->the_post();
get_template_part('template-parts/content', 'event');
}
?>
<p class="t-center no-margin"><a href="<?php echo get_post_type_archive_link('event') ?>" class="btn btn--blue">View All Events</a></p>
</div>
</div>
<div class="full-width-split__two">
<div class="full-width-split__inner">
<h2 class="headline headline--small-plus t-center">From Our Blogs</h2>
<?php
$homepagePosts = new WP_Query(array(
'posts_per_page' => 2
));
while ($homepagePosts->have_posts()) {
$homepagePosts->the_post(); ?>
<div class="event-summary">
<a class="event-summary__date event-summary__date--beige t-center" href="<?php the_permalink(); ?>">
<span class="event-summary__month"><?php the_time('M'); ?></span>
<span class="event-summary__day"><?php the_time('d'); ?></span>
</a>
<div class="event-summary__content">
<h5 class="event-summary__title headline headline--tiny"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h5>
<p><?php if (has_excerpt()) {
echo get_the_excerpt();
} else {
echo wp_trim_words(get_the_content(), 18);
} ?> <a href="<?php the_permalink(); ?>" class="nu gray">Read more</a></p>
</div>
</div>
<?php } wp_reset_postdata();
?>
<p class="t-center no-margin"><a href="<?php echo site_url('/blog'); ?>" class="btn btn--yellow">View All Blog Posts</a></p>
</div>
</div>
</div>
关键改动点:
- 为什么不用 WordPress 核心
columns+query-loop拼这个区域:- 视觉上:「背景色撑满全宽、内容限定在居中最大宽度内」这种版式,核心
columnsBlock 做不到(除非放弃对响应式断点的控制权,接受 WordPress 自己那套断点逻辑,跟自定义 CSS 断点冲突) - 查询上:近期活动要按自定义 meta 字段
event_date(不是文章发布日期)筛选「日期 ≥ 今天」并正序排列,这是query-loop这种简化版查询完全表达不出来的复杂条件
- 视觉上:「背景色撑满全宽、内容限定在居中最大宽度内」这种版式,核心
- 核心判断:这块内容不需要任何「可自定义」的选项——跟 Banner 不同(Banner 需要用户决定标题、按钮、颜色、背景图),这里没有任何东西需要用户在编辑器里调整,所以完全不需要花力气搭一套跟前台视觉一致的「所见即所得」编辑体验;只要让它可以被当作一个 Block 插入/拖拽到任意页面(不只是首页)就够了,编辑器里显示一个朴素的占位提示即可
PlaceholderBlock类是JSXBlock的简化版,主要区别:- 脚本加载路径从
/build/{name}.js改成/our-blocks/{name}.js——因为这类 Block 的 JS 极其简单(只是一句占位文字),不需要用 JSX,也就不需要经过@wordpress/scripts的构建/转译流程;直接把源文件当成最终文件来加载,省去了「写进package.json的start脚本 → 编译到build/→ 加载编译产物」这一整套折腾 - PHP
render_callback强制开启,不是像JSXBlock那样可选——因为这个类存在的意义就是「纯 PHP 驱动、无 JS 编辑交互」的占位 Block,不需要给「要不要开启 PHP 渲染」留选项
- 脚本加载路径从
- JS 文件不写 JSX,直接用
wp.blocks.registerBlockType()+wp.element.createElement()(React 的原生调用方式):createElement(标签名, props对象, 子内容)三个参数——第一个是要创建的 HTML 标签名,第二个是这个元素的属性(这里只给了className),第三个是它的内容/子节点(这里是一段占位文字)——这是 JSX 编译后实际会变成的样子,直接手写省去了 JSX 语法糖,但代价是可读性差一些,适合这种极其简单、不值得引入构建流程的场景 save函数返回null:跟插件章节学过的模式一致,save不负责任何前台输出(完全交给 PHPrender_callback)className: "our-placeholder-block"配合shame.css:给占位 Block 一点基础视觉(内边距、居中文字、灰色渐变背景),shame.css是这套 CSS 组织习惯里专门收纳「不追求分类整洁的快速修复」的文件(这门课主题章节沿用下来的命名传统),不需要为这几行样式专门建一个新的 SCSS 模块文件eventsandblogs.php的内容是从传统主题front-page.php原样搬运过来的既有代码,一个字都没改:因为这段查询逻辑(WP_Query按event_datemeta 筛选未来活动、get_template_part()复用现成的content-event.php局部模板、博文列表查询等)在传统主题章节早就写好、验证过,直接复制粘贴过来即可,不需要为了「转换成 Block」重新设计这套逻辑- 可选的进阶思路(作者提到但没有采用):如果想让编辑器里的占位也变成「所见即所得」的真实预览,理论上可以再写一个自定义 REST 端点、配合 JS 组件实现——但作者明确表示不打算这么做,因为这块内容本身没有可编辑项,做一份「看起来一样但不能编辑」的预览反而容易让用户误以为能点击修改,増加困惑而非帮助
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
PlaceholderBlock(自定义类) | 可复用 PHP 类 | 注册「无需 JS 编辑交互、纯 PHP 渲染」的占位 Block,脚本无需经过构建流程 |
wp.element.createElement(标签, props, 子内容) | React 原生调用(WordPress 别名) | 不写 JSX 时手动创建 React 元素的方式 |
get_template_part($slug, $name) | WP 内建 function | 加载可复用的模板局部文件(这里复用传统主题已有的 content-event.php) |
常见坑
- 想用 WordPress 核心
columns+query-loop硬凑出复杂查询/复杂版式——query-loop无法表达按自定义 meta 筛选 + 排序这类需求,columns也没法完全摆脱 WordPress 自身的响应式断点限制 - 给完全不需要用户自定义任何内容的区域,也照搬 Banner 那一套「所见即所得」JS 编辑体验——不仅是浪费开发时间,还可能让用户误以为这块内容可以点击编辑
- 给这种极简单的占位 Block 也走完整的
@wordpress/scripts构建流程——完全没必要,直接手写不用 JSX 的原生 JS,省掉不必要的构建步骤
[截图:编辑器里插入 Events and Blogs Block 后显示的灰色占位提示条,以及前台该区域真正渲染出的近期活动/最新博文两栏内容]
延伸 / 后续讲座会用到
下一讲继续用这套「PHP 驱动的占位 Block」思路,搭建页头/页脚等其余模板部分。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 28, EP199, EP200