EP194-196. “MediaUpload 背景图选择器与 apiFetch 查特定尺寸 URL”
🔒 登录后可标记已读📌 说明:EP194 的 transcript 在「First, let's just make sure that we're holding on to this ID value in a block attribute.」这句话结束,EP196 开头是同一句话的重复接续,中间夹了 EP195 一条不含实操步骤的「Quick Note」——三篇合并成一篇笔记。
📌 并入 EP195 的提醒:这一讲涉及的 PHP 判断(if (!$attributes['imgURL']))在较新版本的 PHP 环境下,如果这个属性压根不存在会触发「访问未定义数组键」的警告,正确写法应该是 if (!isset($attributes['imgURL']))。不过要注意——对照这一讲实际的代码快照,banner.php 里这处判断当时仍然写的是 !$attributes['imgURL'],没有同步改成 isset() 版本,这是作者口头提醒但代码快照没跟上的一处遗留细节,如果在自己的环境中遇到警告,可以按提醒自行改成 isset() 版本。
给 Banner Block 加上「在右侧检查器面板选择/上传自定义背景图」的功能。用 WordPress 内建的 MediaUpload(配合 MediaUploadCheck 做权限校验)打开标准的媒体库选择窗口,选中图片后只拿到一个媒体 ID(imgID),但界面上需要的是「裁切成 Banner 专用宽高比(pageBanner 尺寸,主题章节已经注册过)的那张图的 URL」——默认返回的数据不包含自定义尺寸,所以要额外用 apiFetch 发一次请求,专门查这个媒体 ID 对应的 pageBanner 尺寸 URL,存进 imgURL 属性。PHP 端的 render_callback 直接使用已经存好的 imgURL,如果完全没设置过(imgURL 为空)就退回写死的默认背景图。
涉及文件
wp-content/themes/fictional-university-block-theme/our-blocks/banner.js(修改)wp-content/themes/fictional-university-block-theme/our-blocks/banner.php(修改)
代码实现
our-blocks/banner.js(完整文件):
import apiFetch from "@wordpress/api-fetch"
import { Button, PanelBody, PanelRow } from "@wordpress/components"
import { InnerBlocks, InspectorControls, MediaUpload, MediaUploadCheck } from "@wordpress/block-editor"
import { registerBlockType } from "@wordpress/blocks"
import { useEffect } from "@wordpress/element"
registerBlockType("ourblocktheme/banner", {
title: "Banner",
supports: {
align: ["full"]
},
attributes: {
align: { type: "string", default: "full" },
imgID: { type: "number" },
imgURL: { type: "string" }
},
edit: EditComponent,
save: SaveComponent
})
function EditComponent(props) {
useEffect(
function () {
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 (
<>
<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>
</>
)
}
function SaveComponent() {
return <InnerBlocks.Content />
}
our-blocks/banner.php:没有自定义背景图时退回默认图:
<?php
if (!$attributes['imgURL']) {
$attributes['imgURL'] = get_theme_file_uri('/images/library-hero.jpg');
}
?>
<div class="page-banner">
<div class="page-banner__bg-image" style="background-image: url('<?php echo $attributes['imgURL'] ?>')"></div>
<div class="page-banner__content container t-center c-white">
<?php echo $content; ?>
</div>
</div>
关键改动点:
- 新增两个属性:
imgID(number,选中的媒体附件 ID)、imgURL(string,实际要用的图片地址)——两个都存,而不是只存 ID 现查,是为了让 PHP 端不用重新执行一次查询,直接读imgURL就够用 MediaUploadCheck+MediaUpload——标准的媒体选择器套路:MediaUploadCheck包在最外层,负责检查当前登录用户是否有权限上传媒体(没权限的话内部内容不会显示);MediaUpload是真正的选择器组件,用三个 prop 驱动:value:当前已选中的媒体 ID,用来在重新打开选择窗口时正确高亮显示「这张已经是你选过的」onSelect:用户选好图片、点击选择器窗口里的「Select」按钮后触发,拿到的参数是一个描述这个媒体的对象(这一讲用打印到控制台的方式实地查看过它的结构,最终只用到里面的.id字段)render:这是一个「返回 JSX 决定触发按钮长什么样」的函数,拿到一个解构出来的open方法——把这个open绑定到自己写的按钮的onClick上,点击就会弹出标准的 WordPress 媒体库窗口
onFileSelect(x) { props.setAttributes({imgID: x.id}) }:只从选中结果里取.id,存进imgID- 默认返回的媒体数据不包含自定义图片尺寸:WordPress 原生返回的媒体对象只带
full/large/medium/thumbnail这几个内建尺寸的 URL,主题章节自己注册的pageBanner自定义宽高比不在这份默认数据里——所以需要额外单独查一次 - 用
apiFetch按媒体 ID 单独查一次完整数据:请求路径是/wp/v2/media/{ID}(WordPress 内建的媒体 REST 端点),这次返回的完整 JSON 里能在media_details.sizes.pageBanner.source_url这条路径下找到需要的 URL - 用
useEffect监听imgID变化去发这次请求,而不是直接放在onFileSelect里:作者解释的理由——如果只在「刚选完图片」那一刻查一次,那么「页面刷新后重新加载已经存过的imgID,但还没有对应imgURL」这种情况就没法覆盖到;用useEffect监听imgID本身,不管这个值是「用户刚选的」还是「刷新页面后从数据库读回来的」,只要它存在/变化,都会触发一次查询,更可靠 useEffect内部同样要包一层async function go() {...}再立即调用:这门课反复出现的固定写法,因为useEffect本身不能直接接收一个async函数- 前端展示
imgURL用模板字符串拼接:`url('${props.attributes.imgURL}')` - PHP 端的默认图回退逻辑:
if (!$attributes['imgURL']) { $attributes['imgURL'] = get_theme_file_uri(...) }——如果这个属性是空字符串/不存在(用户从来没选过自定义背景图),就临时把它改写成默认的library-hero.jpg路径;后面统一echo $attributes['imgURL']输出,不用写两套不同的输出逻辑 - 为什么要把最终选定的 URL(而不是只有 ID)存进 attribute、PHP 端直接读:跟 EP193 一致的设计思路——避免在 PHP 里重新写一遍「根据 ID 查特定尺寸 URL」的查询逻辑,JS 端已经查好存下来的值可以直接复用
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
MediaUploadCheck | React 组件(@wordpress/block-editor) | 校验当前用户是否有权限上传媒体,包裹住实际的选择器 |
MediaUpload | React 组件(@wordpress/block-editor) | 标准媒体选择器,value/onSelect/render 三个 prop 驱动交互 |
apiFetch({path, method})(@wordpress/api-fetch) | WordPress 官方 JS 工具 | 发送带认证的 REST API 请求,这里用来查询 /wp/v2/media/{ID} |
useEffect(callback, [依赖])(@wordpress/element) | React Hook | 监听某个属性变化(这里是 imgID)时重新查询对应的图片 URL |
常见坑
- 只在
onSelect触发的那一刻查一次图片 URL——刷新页面后重新加载已存的imgID时不会重新查询,容易导致imgURL缺失或过期,更稳妥的做法是用useEffect监听imgID本身 - 以为选中媒体后返回的数据自带所有自定义图片尺寸——默认只包含内建的
full/large/medium/thumbnail,自定义尺寸需要额外单独请求/wp/v2/media/{ID}才能拿到 - PHP 端判断「是否有自定义背景图」时直接用
!$attributes['imgURL']而不加isset()——较新 PHP 环境下如果这个键完全不存在会触发警告(EP195 提醒,注意这一讲的代码快照本身还没有修正这一点) useEffect内部直接把回调函数标记成async——不被支持,需要在内部另外定义并立即调用一个async function
[截图:Banner Block 右侧检查器面板点击 Choose Image 按钮后弹出的 WordPress 标准媒体库选择窗口]
延伸 / 后续讲座会用到
下一讲要处理一个小细节:让默认背景图的路径更「健壮」,不要写死假设 WordPress 安装在根目录。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 28, EP194, EP195, EP196