iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0

我們開始來做第一個 WordPress 自訂區塊。重新複習一下靜態區塊的概念:它的輸出固定寫死在文章內容裡,不需要每次載入時用 PHP 重算,這是最單純的區塊型態,先從它建立心智模型,後面的動態區塊、互動區塊都是在這個基礎上長出來的。

先講一個關鍵字:single source of truth

做區塊第一個要認識的檔案是 block.json。它是整個區塊的「單一事實來源」:區塊叫什麼名字、屬於哪個分類、有哪些可調的屬性(attributes)、支援哪些功能、要載入哪些 JS/CSS,全部宣告在這一個檔案裡。WordPress、編輯器、建置工具都讀它。

這跟傳統做法差很多。以前你要做一個「可重複用的內容」,得在 functions.php 註冊 shortcode、另外寫 CSS、enqueue JS,資訊散在三四個地方。block.json 把這些收成一個宣告檔,改一個地方就好。

一個區塊其實是一個資料夾

一個自訂區塊不是單一檔案,可以用 blocks/ 底下的一個資料夾來整理,裡面幾個檔案各司其職。以這個 eyebrow 為例:

blocks/eyebrow/
├── block.json   # 宣告:名字、屬性、要載入哪些 JS/CSS(就是剛剛那個「單一事實來源」)
├── index.js     # 編輯器端的 JavaScript:edit 怎麼顯示、save 存成什麼
└── style.css    # 這個區塊的樣式

以下逐步介紹每個檔案的內容。首先是 block.json

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "block-theme/eyebrow",
  "title": "Eyebrow",
  "category": "theme",
  "textdomain": "block-theme",
  "attributes": {
    "text": { "type": "string", "source": "html", "selector": "span" }
  },
  "supports": { "html": false },
  "editorScript": "file:./index.js",
  "style": "file:./style.css"
}

這段不長,但每個欄位都有它的職責,快速掃一遍:

  • $schema:指向 block.json 的官方結構定義,讓編輯器和 IDE 能幫你自動補全、檢查有沒有寫錯欄位。純輔助,不影響功能。
  • apiVersion:用哪一代的 Block API,現在一律填 3(最新、支援 iframe 編輯器那套)。
  • name:區塊的唯一識別,格式是「命名空間/區塊名」。這裡是 block-theme/eyebrow,命名空間用主題的 slug,避免跟別人的區塊撞名。
  • title:在編輯器插入器(inserter)裡顯示的名稱,使用者實際看到、搜尋到的就是它。
  • category:這個區塊歸在插入器的哪一個分類。theme 是我們自己註冊的分類,把主題的自訂區塊整合在一起。
  • textdomain:i18n 的翻譯網域,要跟主題一致(block-theme),區塊裡的 __() 字串才翻得到。
  • attributes:這個區塊「能存哪些值」。這裡宣告一個 text,型別是字串。
  • supports:設定這個區塊要不要擁有核心內建的能力(對齊、顏色⋯,Day 19 專講)。這裡 "html": false 是關掉「以 HTML 原始碼編輯」那顆選項,免得使用者手改壞結構。
  • editorScript:這個區塊在「編輯器端」要載入的 JS,file:./index.js 指的就是同資料夾的 index.js
  • style:前後台都會載入的樣式檔,file:./style.css

其中 attributes.text 用了 "source": "html""selector": "span",意思是這個值直接從存下來的 HTML 的 <span> 裡撈,不另外存資料庫,這正是「靜態」的特徵。

回頭看剛剛 block.json 的最後兩行:"editorScript": "file:./index.js""style": "file:./style.css",它們就是在指「同一個資料夾裡的 index.jsstyle.css」。換句話說,block.json 是索引,真正的行為寫在它指到的 index.js。所以接下來要出現的那段 JS,不是憑空冒出來的,它就是 index.js 的內容。(靜態區塊就這三個檔案;等 Day 20 做動態區塊時,資料夾會多一個 render.php,把渲染交給 PHP。)

一個區塊的核心功能:edit 與 save

接下來看最關鍵的 index.js。每個區塊裡面都有兩個要寫的函式:editsave

  • edit:決定「在後台編輯器裡」長什麼樣、怎麼操作。使用者在後台看到能點能改的行為都定義在這個函式。
  • save:決定「存進資料庫後給前台看」的 HTML 長什麼樣。靜態區塊的重點就在這裡:save 回傳的 markup 會直接寫進文章內容,前台載入時原封不動輸出,不經過 PHP。

這是 index.js 的完整內容,用 RichText 做一個可就地編輯文字的 eyebrow:

import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, RichText } from '@wordpress/block-editor';
import metadata from './block.json';

registerBlockType( metadata.name, {
  edit( { attributes, setAttributes } ) {
    const blockProps = useBlockProps();
    return (
      <RichText
        { ...blockProps }
        tagName="span"
        value={ attributes.text }
        onChange={ ( text ) => setAttributes( { text } ) }
        placeholder="輸入標語…"
      />
    );
  },
  save( { attributes } ) {
    const blockProps = useBlockProps.save();
    return <RichText.Save { ...blockProps } tagName="span" value={ attributes.text } />;
  },
} );

先看開頭三個 import 和註冊:

  • import { registerBlockType } from '@wordpress/blocks';:從核心的 blocks 套件拿 registerBlockType,這是「向 WordPress 註冊一個區塊」的函式。
  • import { useBlockProps, RichText } from '@wordpress/block-editor';:拿兩個編輯器工具。useBlockProps 幫區塊外層套上該有的 class 和屬性;RichText 是可即時編輯的文字元件。
  • import metadata from './block.json';:把同資料夾的 block.json 讀進來,等下直接用它的 name,不用再手打一次區塊名字。
  • registerBlockType( metadata.name, { … } );:用 block.json 裡的名字(block-theme/eyebrow)註冊區塊;第二個參數這個物件,裝的就是 editsave 兩個函式。

再看 edit,它決定「編輯器裡」怎麼顯示:

  • edit( { attributes, setAttributes } ) {:編輯器要畫這個區塊時會呼叫它,並遞給你目前的 attributes(現在的值)和 setAttributes(改值的函式)。
  • const blockProps = useBlockProps();:產生區塊外層必要的 props(class、data 屬性等)。少了它,區塊在編輯器裡會缺少框選、對齊這些預設行為。
  • <RichText … />:回傳畫面,就是一個可直接打字的文字欄位。裡面幾個屬性:
    • { ...blockProps }:把上一行那組 props 組合進去。
    • tagName="span":這段文字實際渲染成 <span>
    • value={ attributes.text }:顯示目前存的文字。
    • onChange={ ( text ) => setAttributes( { text } ) }:使用者每改一次字,就把新文字寫回 attributes.text
    • placeholder="輸入標語…":還沒有內容時顯示的提示字。

最後看 save,它決定儲存到資料庫給前台顯示的 HTML:

  • save( { attributes } ) {:要存檔、輸出前台時呼叫,只需要 attributes,不需要 setAttributes(前台不編輯)。
  • const blockProps = useBlockProps.save();save 專用的 blockProps,套在真正要寫進資料庫的那段 HTML 上。
  • return <RichText.Save … />:用 RichText.Saveattributes.text 輸出成 <span>,這段 HTML 會直接寫進文章內容

一句話總結:edit 裡用 RichText 讓使用者就地打字,改動透過 setAttributes 存進 attributes.textsaveRichText.Save 把同一個值輸出成 <span>,兩邊 tagName 一致,這樣前後台的呈現結果才會一樣。

靜態區塊的甜蜜點與陷阱

靜態區塊最大的好處是:前台不跑 PHP,就是一段現成 HTML,效能最好,適合「內容完成後就固定」的東西:一段標語、一個引言、一塊 CTA 文案。

但它有個著名的陷阱:block validation(區塊驗證)。因為 markup 存死在資料庫,一旦你改了 save 的輸出結構(多包一層 div、換個 class),舊文章裡存的舊 HTML 就跟新的 save 對不上,編輯器會跳「此區塊包含非預期或無效的內容」。這時除非用 deprecation 保留舊版,或是讓 AI 幫你批次轉換。

這也是為什麼:當內容需要「每次載入重算」時我們會改用動態區塊,把渲染交給 PHP 就沒有這個問題。

讓 AI 幫你生這一切

實務上我不會手刻上面每個檔案,我會跟 Claude Code 說:「幫我在 blocks/eyebrow/ 做一個靜態區塊,一個 text 屬性,用 RichText 輸入元件,輸出成 span。」它會照 block.json + edit/save 的慣例把檔案生齊,因為 wp-block-themes 技能裡就寫著這套慣例。

你要做的是看懂它生的對不對,而你今天已經有這個能力了。第一個區塊做出來了。下一篇我們把焦點放在區塊編輯器的設定功能,我們只要在 block.json 動一個欄位:supports,就能立刻擁有完整的對齊、顏色、間距的控制項,完全不用自己寫。

文章目錄:https://oberonlai.blog/category/2026-ithome/


上一篇
讀懂 AI 產生的 WordPress 區塊主題架構
下一篇
Block Supports 內建開箱即用的設定區塊設定欄位
系列文
從一句話到一個網站:用 Vibe Coding 開發 WordPress Block Theme 的 30 天19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言