EP219. “迁移 Banner:attributes 搬进 block.json 与 wp_localize_script 给回退图”
🔒 登录后可标记已读迁移这一章第一个「真正复杂」的 Block——Banner(背景图选择器 + 嵌套标题/按钮)。跟迁移占位 Block 的流程大体一致,但多了几个新问题:Block 的 attributes 要从 JS 对象搬进 block.json(JSON 里不能写 JS 表达式/变量,只能写字面量,原本用 JS 变量算出来的默认值需要改用别的机制处理);edit/save 分开成独立文件时要注意依赖导入不能漏(比如 InnerBlocks 需要在用到它的每个文件里各自导入一次);旧的 JSXBlock 类里通过构造函数第三参数注入的「主题目录路径」,换一种方式实现——直接在批量注册 Block 的函数里用 wp_localize_script() 挂在官方的 wp-editor 脚本上,不再依赖自定义类。
涉及文件
wp-content/themes/fictional-clean-blocks/functions.php(修改,注释旧的JSXBlock调用、新增wp_localize_script+register_block_type_from_metadata;新增/迁移全部PlaceholderBlock占位 Block;同时清理search板块render.php里过时的 while 循环)wp-content/themes/fictional-clean-blocks/src/banner/block.json(新建)wp-content/themes/fictional-clean-blocks/src/banner/edit.js(新建,从旧的our-blocks/banner.js搬运)wp-content/themes/fictional-clean-blocks/src/banner/index.js(新建)wp-content/themes/fictional-clean-blocks/src/banner/render.php(新建,从旧的our-blocks/banner.php搬运)
代码实现
src/banner/block.json(新建):
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "ourblocktheme/banner",
"title": "Fictional University Banner",
"supports": {
"align": ["full"]
},
"attributes": {
"align": { "type": "string", "default": "full" },
"imgID": { "type": "number" },
"imgURL": { "type": "string" }
},
"editorScript": "file:./index.js",
"render": "file:./render.php"
}
src/banner/edit.js(完整文件,从旧的 our-blocks/banner.js 搬运,imgURL 的默认回退逻辑改用 useEffect 实现):
import apiFetch from "@wordpress/api-fetch"
import { Button, PanelBody, PanelRow } from "@wordpress/components"
import {
useBlockProps,
InnerBlocks,
InspectorControls,
MediaUpload,
MediaUploadCheck
} from "@wordpress/block-editor"
import { registerBlockType } from "@wordpress/blocks"
import { useEffect } from "@wordpress/element"
export default function Edit(props) {
const blockProps = useBlockProps()
useEffect(function () {
if (!props.attributes.imgURL) {
props.setAttributes({ imgURL: ourThemeData.themePath + "/images/library-hero.jpg" })
}
}, [])
useEffect(
function () {
if (props.attributes.imgID) {
async function go() {
const response = await apiFetch({
path: `/wp/v2/media/${props.attributes.imgID}`,
method: "GET"
})
props.setAttributes({ imgURL: response.media_details.sizes.pageBanner.source_url })
}
go()
}
},
[props.attributes.imgID]
)
function onFileSelect(x) {
props.setAttributes({ imgID: x.id })
}
return (
<div {...blockProps}>
<InspectorControls>
<PanelBody title="Background" initialOpen={true}>
<PanelRow>
<MediaUploadCheck>
<MediaUpload
onSelect={onFileSelect}
value={props.attributes.imgID}
render={({ open }) => {
return <Button onClick={open}>Choose Image</Button>
}}
/>
</MediaUploadCheck>
</PanelRow>
</PanelBody>
</InspectorControls>
<div className="page-banner">
<div
className="page-banner__bg-image"
style={{ backgroundImage: `url('${props.attributes.imgURL}')` }}
></div>
<div className="page-banner__content container t-center c-white">
<InnerBlocks
allowedBlocks={["ourblocktheme/genericheading", "ourblocktheme/genericbutton"]}
/>
</div>
</div>
</div>
)
}
src/banner/index.js(完整文件,save 因为只有一行直接内联,不单独拆文件):
import { InnerBlocks } from "@wordpress/block-editor"
import { registerBlockType } from "@wordpress/blocks"
import metadata from "./block.json"
import Edit from "./edit"
registerBlockType(metadata.name, {
edit: Edit,
save: function () {
return <InnerBlocks.Content />
}
})
functions.php:批量注册函数里用 wp_localize_script 挂在官方 wp-editor 脚本上,注入主题目录路径:
function our_new_blocks() {
wp_localize_script('wp-editor', 'ourThemeData', array('themePath' => get_stylesheet_directory_uri()));
register_block_type_from_metadata(__DIR__ . '/build/banner');
register_block_type_from_metadata(__DIR__ . '/build/footer');
register_block_type_from_metadata(__DIR__ . '/build/header');
register_block_type_from_metadata(__DIR__ . '/build/eventsandblogs');
// ...后续讲座逐个迁移完成的其余 Block,同样一行一个...
}
add_action('init', 'our_new_blocks');
关键改动点:
attributes要从 JS 对象搬进block.json:跟标题一样,register_block_type_from_metadata()依赖block.json里声明好的attributes字段,不再从 JS 里的registerBlockType()第二个参数读取——把原本 JS 里attributes: {...}这一整段对象搬到block.json,用双引号包住每个属性名(JSON 语法要求)supports同样搬进block.json({"align": ["full"]}),道理一致- JSON 不能写 JS 表达式,
imgURL的默认值处理方式要变:原本旧版本 JS 里imgURL: {type: "string", default: banner.fallbackimage}这种写法,default的值是一个动态变量(依赖wp_localize_script()注入的全局对象)——但block.json是纯 JSON,只能写死字面量,没法在这里引用运行时才存在的变量。这一讲索性去掉imgURL的default,改用一个新的useEffect(依赖数组为空,只在组件首次挂载时运行一次)在 JS 里判断「如果还没有imgURL,就手动setAttributes一个默认值」——效果等价,只是实现方式从「声明式默认值」改成了「运行时判断补齐」 edit.js/index.js分拆时,两个文件各自需要的 import 不能漏:InnerBlocks在edit.js(编辑器里显示可插入子 Block 的区域)和index.js(save函数里的InnerBlocks.Content)两处都要用到,必须各自独立import一次——这是这一讲踩到的一个真实 bug(一开始只在edit.js顶部导入过一次,忘记在index.js也导入,导致save阶段报「InnerBlocks未定义」)Edit函数必须声明props参数:这一讲另一个真实踩到的 bug——占位 Block 因为没有任何动态数据,Edit()不需要参数;但但凡涉及attributes/setAttributes的复杂 Block,忘记在export default function Edit(props)里声明props参数,会直接报「props未定义」——排查思路是善用浏览器控制台报错信息,作者提到「与其一次性追求完美,不如先跑起来看报错信息,通常是最快的排错方式」save只有一行代码时,不单独拆save.js文件:直接在index.js的registerBlockType()调用里内联一个匿名函数save: function() { return <InnerBlocks.Content /> }——这是跟「edit逻辑复杂就拆文件」对称的判断标准:代码量决定要不要拆分,不是一刀切wp_localize_script()换了一种「挂靠对象」:上一章的JSXBlock类是把数据挂在自己那个 Block 专属注册的脚本 handle 上(比如banner);这一讲改成统一挂在 WordPress 官方内置的wp-editor这个脚本 handle 上——因为不管哪个 Block 的编辑器脚本,都必然会依赖wp-editor这个基础包,所以把这份「主题目录路径」数据挂在这个通用脚本上,任何 Block 的 JS 都能读到全局变量ourThemeData.themePath,不需要每个 Block 各自单独注入一份- 在
our_new_blocks()函数(所有 Block 的批量注册入口)里、任何register_block_type_from_metadata()调用之前调用一次wp_localize_script()即可,不需要为每个 Block 分别调用 - 顺手清理了「占位 Block」阶段遗留的一个 while 循环问题:
search相关的render.php(无 JS 环境下的搜索结果兜底页面)之前照抄传统主题代码时保留了主查询的while循环,导致这个页面完全空白——按 EP208 的规则删掉这个循环后问题解决,这提醒了「迁移到新系统」和「WP 6.4 单篇内容不需要 while 循环」是两件独立的事,即使是最简单的占位 Block 也可能踩到这个坑 - 调试思路:先让代码跑起来,根据浏览器控制台报错逐条修:这一讲连续出现「
InnerBlocks未定义」「props未定义」两个错误,作者没有试图一次性把所有东西都想清楚再写代码,而是先保存看报错、照着报错信息定位问题——这是处理这类「从旧代码搬运到新结构」场景时更高效的实际工作方式
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
wp_localize_script('wp-editor', $变量名, $数据数组) | WP 内建 function | 挂靠在官方 wp-editor 脚本上注入全局数据,任何 Block 编辑器 JS 都能读到,不需要各自单独注入 |
useEffect(callback, [])(首次挂载时判断并补齐默认值) | React 用法 | 替代 block.json 里无法用动态值声明的 default,运行时判断补齐 |
常见坑
- 试图在
block.json的attributes.default里写一个 JS 变量/表达式——JSON 是纯数据格式,不支持这种写法,必须写字面量或者干脆不写默认值、改用 JS 运行时逻辑补齐 edit.js/index.js拆成两个文件后,某个共用的依赖(比如InnerBlocks)只在其中一个文件里import——另一个文件用到时会报「未定义」- 复杂 Block 的
Edit/Save函数忘记声明props参数——只要用到attributes/setAttributes就必须要这个参数,简单占位 Block 不需要,但一旦涉及动态数据就必须补上 - 只把「已迁移到
block.json」当作唯一要检查的事,忽略了「WP 6.4 单篇内容不需要 while 循环」这条独立规则同样要应用到迁移后的render.php文件里
延伸 / 后续讲座会用到
下一讲继续迁移标题(genericheading)和按钮(genericbutton)两个 Block。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 29, EP219