WP DEVELOP

EP199-200. “PlaceholderBlock 类:纯 PHP 驱动、无需构建的占位 Block”

首页 WordPress 开发课程 BLOCK THEME(2024 最佳实践) · EP199-200
约 25 分钟· #EP199-200#BLOCK THEME(2024 最佳实践)
🔒 登录后可标记已读

📌 说明: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 拼这个区域
    • 视觉上:「背景色撑满全宽、内容限定在居中最大宽度内」这种版式,核心 columns Block 做不到(除非放弃对响应式断点的控制权,接受 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.jsonstart 脚本 → 编译到 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 不负责任何前台输出(完全交给 PHP render_callback
  • className: "our-placeholder-block" 配合 shame.css:给占位 Block 一点基础视觉(内边距、居中文字、灰色渐变背景),shame.css 是这套 CSS 组织习惯里专门收纳「不追求分类整洁的快速修复」的文件(这门课主题章节沿用下来的命名传统),不需要为这几行样式专门建一个新的 SCSS 模块文件
  • eventsandblogs.php 的内容是从传统主题 front-page.php 原样搬运过来的既有代码,一个字都没改:因为这段查询逻辑(WP_Queryevent_date meta 筛选未来活动、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