Gutenberg 開發不只前端!資深工程師帶你打造「PHP 動態區塊」,後端資料隨你玩!
嗨,我是浪花科技的資深工程師 Eric。寫了這麼多年的 WordPress,我看著它從一個單純的部落格平台,一路演變成現在這個功能強大的 CMS。說到這幾年最大的變革,絕對非 Gutenberg 區塊編輯器莫屬。一開始嘛,老實說,我也跟很多老開發者一樣,覺得這東西綁手綁腳,還是傳統編輯器+ACF 香。但當我真正撩下去研究後,才發現「真香」的其實是 Gutenberg 的無限可能性,特別是我們今天要聊的主角——「動態區塊 (Dynamic Blocks)」。
很多剛接觸 Gutenberg 區塊開發的朋友,可能都從「靜態區塊」入手。就是那種你在編輯器裡輸入文字、放張圖片,它就原封不動地存進資料庫,前端再把它顯示出來。這很棒,但如果…你想做一個「最新文章列表」區塊呢?或是一個「熱門商品推薦」區塊?總不能每次發新文章、換了熱門品,還得手動去每個頁面更新這個區塊吧?那也太蠢了,工程師的懶惰是推動世界進步的動力,這種重複性的工作絕對不能忍!
這就是動態區塊登場的時刻了。它允許我們用大家最熟悉的 PHP 來處理渲染邏輯,直接在伺服器端抓取最新資料、執行複雜運算,再把熱騰騰的 HTML 結果送到使用者面前。這代表什麼?這代表你的區塊從此「活」了過來,它不再是死的內容,而是一個能與你 WordPress 後台資料即時互動的智慧元件。今天,就讓我這個老司機帶你從頭到尾,打造一個實用的 PHP 動態區塊,讓你徹底釋放 Gutenberg 的真正潛力!
為什麼你該擁抱「動態區塊」?它跟靜態區塊、ACF Blocks 差在哪?
在我們動手寫 Code 之前,觀念要先搞清楚,不然很容易走火入魔。我知道很多開發者很愛用 ACF Pro 提供的 ACF Blocks 功能,它確實很方便,但了解底層差異,你才能為專案做出最好的技術選擇。
動態區塊 vs. 靜態區塊
這兩兄弟最大的差別在於「內容是誰決定的」以及「何時決定的」。
- 靜態區塊 (Static Block):內容在「儲存文章時」就決定了。你在編輯器裡看到的,基本上就是它存進
post_content欄位的最終 HTML。它的優點是純粹,因為前端不需要額外處理,就是一段 HTML。缺點也很明顯:內容是死的,無法自動更新。 - 動態區塊 (Dynamic Block):內容在「頁面載入時」才決定。存進資料庫的,只有這個區塊的設定值(我們稱為 attributes),例如「顯示 5 篇文章」、「分類為技術文章」等。當使用者瀏覽頁面時,WordPress 才會根據這些設定值,執行對應的 PHP 程式碼來即時生成 HTML。優點就是靈活、永遠顯示最新資訊;缺點是伺服器需要多花一點點力氣去渲染它(但搭配快取機制,這點效能損耗根本不是問題)。
動態區塊 vs. ACF Blocks
ACF Blocks 其實就是動態區塊的一種「簡化版」實作。它幫你處理了區塊註冊、欄位定義等繁瑣的事情,你只需要專心寫 PHP 模板就好。這對於快速開發、或是不熟悉 React/JavaScript 的開發者來說,是個很棒的入門磚。
但是,天下沒有白吃的午餐。ACF 的便利性犧牲的是「編輯器體驗的極致控制」。使用原生的 Gutenberg 開發,你可以用 React 打造出非常複雜、互動性極高的編輯器介面,例如即時預覽、拖拉排序、非同步載入選項等等。這些是 ACF Blocks 比較難做到的。簡單來說:
- ACF Blocks:開發快速,適合後端欄位多、前端顯示邏輯相對單純的情境。適合不熟前端工具鏈的 PHP 開發者。
- 原生動態區塊:學習曲線較陡,需要 Node.js 環境與 React 基礎。但能提供最完整、最客製化的後台編輯體驗,是打造高階產品或複雜網站的首選。
囉嗦了這麼多,結論就是,想成為頂尖的 WordPress 開發者,原生動態區塊是你非學不可的屠龍技。
實戰開始:打造一個「最新文章列表」動態區塊
講了那麼多理論,是時候來點硬派的了。我們來做一個很常見、也很實用的功能:一個可以讓編輯自由選擇「文章分類」和「顯示數量」的最新文章列表區塊。
Step 1: 環境準備與鷹架搭建
現在的 WordPress 開發早就不是那個一把 FTP 走天下的時代了。你需要 Node.js 環境,這是執行現代前端工具鏈的基礎。沒有的話,請先去 官網安裝 LTS 版本。
接著,打開你的終端機,cd 到你的外掛目錄 (wp-content/plugins),然後執行 WordPress 官方提供的神器指令:
npx @wordpress/create-block latest-posts-dynamic
這個指令會自動幫你建立一個名為 latest-posts-dynamic 的外掛資料夾,並且把開發區塊所需的所有檔案、相依套件都準備好。根本是開發懶人包,不用實在對不起自己。完成後,記得進 WordPress 後台啟用這個新外掛。
Step 2: 改造 block.json,宣告動態身份
鷹架搭好了,我們要先來改一下藍圖。打開 latest-posts-dynamic/block.json 這個檔案,這是我們區塊的「身份證」。找到它,然後加上一個關鍵屬性:"render"。
{
"$schema": "https://schemas.wp.org/wp/trunk/block.json",
"apiVersion": 3,
"name": "create-block/latest-posts-dynamic",
"version": "0.1.0",
"title": "Latest Posts (Dynamic)",
"category": "widgets",
"icon": "megaphone",
"description": "A dynamic block to display a list of latest posts from a selected category.",
"attributes": {
"numberOfPosts": {
"type": "number",
"default": 5
},
"postCategory": {
"type": "string",
"default": ""
}
},
"supports": {
"html": false
},
"textdomain": "latest-posts-dynamic",
"editorScript": "file:./build/index.js",
"editorStyle": "file:./build/index.css",
"style": "file:./build/style.css",
"render": "file:./render.php"
}
看到沒?就是最後那行 "render": "file:./render.php"。它像是在跟 WordPress 說:「嘿!我這個區塊不安於現狀,不要把我存成死死的 HTML。當有人要看我的時候,請去執行我目錄下的 render.php 這個檔案,讓它來決定我該長什麼樣子!」
我們也順便在 attributes 裡定義了兩個屬性:numberOfPosts (文章數量) 和 postCategory (文章分類 ID),並給了它們預設值。這就是我們要存在資料庫的「設定值」。
Step 3: 打造編輯器介面 (edit.js)
接下來是前端工程師的回合。我們要打造一個讓使用者可以設定文章數量和分類的操作介面。打開 src/edit.js,把它改成下面的樣子。別怕,我會一行一行解釋。
import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl, SelectControl } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import './editor.scss';
export default function Edit({ attributes, setAttributes }) {
const { numberOfPosts, postCategory } = attributes;
const categories = useSelect((select) => {
return select('core').getEntityRecords('taxonomy', 'category');
}, []);
let options = [];
if (categories) {
options = categories.map((category) => ({
label: category.name,
value: category.id,
}));
options.unshift({ label: 'All Categories', value: '' });
}
return (
<div {...useBlockProps()}>
<InspectorControls>
<PanelBody title={__('Block Settings', 'latest-posts-dynamic')}>
<RangeControl
label={__('Number of Posts', 'latest-posts-dynamic')}
value={numberOfPosts}
onChange={(value) => setAttributes({ numberOfPosts: value })}
min={1}
max={10}
/>
<SelectControl
label={__('Category', 'latest-posts-dynamic')}
value={postCategory}
options={options}
onChange={(value) => setAttributes({ postCategory: value })}
/>
</PanelBody>
</InspectorControls>
<p>
{__('Latest Posts Block - Settings are in the sidebar.', 'latest-posts-dynamic')}
</p>
</div>
);
}
程式碼解說:
- 我們從
@wordpress/components引入了PanelBody,RangeControl(數字滑桿),SelectControl(下拉選單) 等官方 UI 元件。 - 使用
InspectorControls把我們的設定選項放在右邊的側邊欄,而不是直接顯示在區塊上,這樣編輯區比較乾淨。 - 最酷的是
useSelect這個 hook。我們用select('core').getEntityRecords('taxonomy', 'category')來非同步地抓取網站所有的文章分類,並動態生成下拉選單的選項。這就是原生開發的威力,體驗超棒! - 當使用者調整滑桿或選擇分類時,我們會呼叫
setAttributes函式來更新我們的屬性值。 - 在編輯器的主畫面,我們只顯示一段提示文字。因為真正的內容要由 PHP 渲染,在編輯器裡做一個一模一樣的預覽太費工了,除非客戶預算給很足(工程師的現實…)。
Step 4: 清空 save.js
這一步最簡單,但也最重要,是很多新手會卡關的地方。打開 src/save.js,把它清空到只回傳 null。
export default function save() {
return null;
}
為什麼?因為我們是「動態區塊」。我們不希望 WordPress 把任何由 JavaScript 產生的 HTML 存進資料庫。我們要讓 PHP 全權負責!如果這裡不回傳 null,你會在編輯器存檔時看到一個很經典的錯誤:「此區塊包含非預期的或無效的內容」,這通常就是因為你讓 JS 存了東西,但前端又是 PHP 渲染的,兩邊對不上,WordPress 就會保護性地跳錯。
Step 5: 撰寫後端渲染邏輯 (render.php)
萬事俱備,只欠東風。在你的外掛根目錄下,手動建立一個新檔案:render.php。這就是我們動態區塊的心臟。
<?php
$args = [
'posts_per_page' => $attributes['numberOfPosts'],
'post_status' => 'publish',
];
if (!empty($attributes['postCategory'])) {
$args['cat'] = $attributes['postCategory'];
}
$latest_posts = new WP_Query($args);
if ($latest_posts->have_posts()) :
echo '<div ' . get_block_wrapper_attributes() . '>';
echo '<ul>';
while ($latest_posts->have_posts()) :
$latest_posts->the_post();
printf(
'<li><a href="%1$s">%2$s</a></li>',
esc_url(get_permalink()),
esc_html(get_the_title())
);
endwhile;
echo '</ul>';
echo '</div>';
endif;
wp_reset_postdata();
程式碼解說:
- 這個檔案會自動接收到一個
$attributes陣列,裡面就裝著我們在編輯器裡設定的numberOfPosts和postCategory。 - 我們用這些屬性來組裝一個標準的
WP_Query參數陣列。 - 接著就是大家最熟悉的 WordPress Loop:建立一個
WP_Query物件,然後用while迴圈把文章一篇篇印出來。 get_block_wrapper_attributes()是個好東西,它會自動幫我們加上區塊預設的 class 等等,讓樣式和對齊功能正常運作。- 最後,記得用
wp_reset_postdata()來重置主查詢,這是 WordPress 開發的好習慣,避免污染到頁面的其他部分。
搞定!現在回到你的終端機,在你的外掛目錄下執行 npm start,它會開始監控你的檔案變更並自動編譯。然後去 WordPress 編輯器新增一個文章,你就能在區塊選擇器裡找到我們閃亮亮的「Latest Posts (Dynamic)」區塊了!試著新增它、調整側邊欄的設定、存檔,然後到前台看看成果吧!你會發現,它完美地根據你的設定顯示了最新的文章列表。
結論:動態區塊是通往高階客製化的鑰匙
今天我們從觀念到實作,完整走過了一遍 WordPress 動態區塊的開發流程。你可能感覺到,這比用 ACF 或 Elementor 拖拉元件要複雜得多,需要懂的工具鏈也更廣。但這正是它的價值所在。
掌握了原生動態區塊的開發,你就不再只是個「網站管理員」或「版型套用師」,你是一位能真正為客戶打造獨一無二、高度整合編輯體驗的「WordPress 開發者」。你可以做出與第三方 API 串接的資訊卡、跟 WooCommerce 庫存連動的產品展示區、甚至是需要複雜後端運算的報表區塊。這條路雖然陡峭,但山頂的風景絕對值得。
希望今天的分享能為你打開一扇新的大門。如果你覺得這篇文章對你有幫助,也別忘了分享給你的開發者朋友。當然,如果你們公司有更複雜的 WordPress 客製化需求,或是想打造一個真正能提升編輯效率的後台系統,別客氣,浪花科技的團隊永遠在這裡。
延伸閱讀:
- WordPress REST API 宇宙大爆炸:從零開始打造你的第一個自訂端點!
- 別再被版型綁架!ACF 終極指南:用客製化欄位打造 WordPress 夢幻後台
- WordPress 開發的任督二脈:搞懂 Action & Filter Hooks,客製化功力大爆發!
對高階 WordPress 開發或系統整合有興趣嗎?
我們的團隊專精於打造高效能、高安全性的客製化 WordPress 解決方案,從企業官網、電商系統到複雜的 API 串接與自動化流程,我們都有豐富的實戰經驗。如果你正在尋找一個能真正理解你商業需求的技術夥伴,歡迎點擊這裡填寫表單,與我們的專家聊聊!
常見問題 (FAQ)
Q1: 動態區塊和靜態區塊最大的不同是什麼?我應該怎麼選擇?
最大的不同在於「內容渲染的時機」。靜態區塊的 HTML 在儲存文章時就固定了,而動態區塊的 HTML 是在每次頁面被瀏覽時,由 PHP 即時生成的。選擇的依據是:如果區塊內容需要「自動更新」或「依賴後端資料」(如最新文章、使用者資訊、商品庫存),就必須使用動態區塊。如果內容是固定的,像是一段特定的引言或是一個設計好的卡片,用靜態區塊即可。
Q2: 為什麼動態區塊的 save.js 檔案要回傳 null?
因為動態區塊的最終 HTML 是由後端 PHP (render.php) 全權負責生成的。如果在 save.js 中回傳了任何 HTML,WordPress 會將其視為「應該被儲存的內容」。當使用者下次編輯文章時,WordPress 會比較「儲存的內容」和「編輯器(edit.js)產生的內容」,如果兩者不符(在動態區塊中幾乎肯定不符),就會觸發「區塊驗證失敗」的錯誤。回傳 null 等於告訴 WordPress:「你不用幫我存任何 HTML,專心存我的屬性(attributes)就好,前端顯示的事交給 PHP。」
Q3: 我可以在編輯器(edit.js)裡即時預覽動態內容嗎?
可以,但會比較複雜。一個常見的作法是利用 WordPress 的 REST API。你可以在 `edit.js` 中使用 `@wordpress/api-fetch` 套件,根據使用者調整的屬性(attributes)即時向一個自訂的 REST API 端點發送請求,該端點執行類似 `render.php` 的邏輯並回傳預覽用的 HTML,然後你再用 React 將這個 HTML 渲染到編輯器畫面中。這能提供絕佳的編輯體驗,但開發成本也相對較高。
Q4: 開發動態區塊,我需要很強的 PHP 功力嗎?
不一定需要到「大神」等級,但你至少需要熟悉 WordPress 的核心開發概念。最基本的是要懂得如何使用 `WP_Query` 來抓取文章、了解 WordPress 的 The Loop 運作方式,以及知道如何安全地輸出資料(例如使用 `esc_html`, `esc_url` 等函式)。如果你本來就有 WordPress 主題或外掛的開發經驗,那上手動態區塊的 PHP 部分基本上是沒有問題的。






