一、提取封面图 URL 的原理和过程
背景:封面图是怎么上传的?
在表单中,你用了 antd
的 Upload
组件来上传封面图:
<Upload
name="image"
listType="picture-card"
action="http://geek.itheima.net/v1_0/upload"
fileList={fileList}
onChange={onUploadChange}
...
>
<PlusOutlined />
</Upload>
上传时会自动把图片发给 action
指定的后端地址,后端返回图片地址。你监听 onChange
拿到图片上传的结果。
fileList
长什么样?
它是一个数组,结构类似这样(成功上传后):
[
{
url: "http://geek.itheima.net/files/2025/04/cover1.png"
},
{
url: "http://geek.itheima.net/files/2025/04/cover2.png"
},
...
]
这些 url
就是上传后返回的图片地址,你可以直接提交给后端。
fileList
是怎么被处理成这个样子的?
看看这个函数:
const cacheImgList = useRef() // 缓存图片,用于封面图切换回显
const onUploadChange = ({ fileList }) => {
const formatList = fileList.map(file => {
// 上传完毕 做数据处理
if (file.response) {
// 上传完成后,提取后端返回的图片 URL
return {
url: file.response.data.url
}
}
// 否则在上传中时,不做处理
return file
})
setFileList(formatList)
// 同时把图片列表存入仓库一份
cacheImgList.current = formatList // 存入缓存
// 作用:在图片上传成功后,把完整的图片列表存一份“缓存”,用于后续切换封面图数量时做“图像恢复”。
}
关键点解释:
-
当你上传图片后,后端会返回一个 JSON 响应:
{
"data": {
"url": "http://geek.itheima.net/files/cover123.jpg"
}
}
-
file.response.data.url
就是这个地址。 -
fileList.map(...)
处理后,我们只保留图片的url
字段,变成一个非常干净的数组。
那提取封面图的时候是怎么做的?
看 onFinish
提交时的代码:
// 获取表单数据
const onFinish = async(values) =>{
// 数据的二次处理 重点是处理cover字段
const { channel_id, content, title, type } = values
// 判断type fileList 是匹配的才能正常提交
const params = {
channel_id,
content,
title,
type,
cover: {
type: type,
images: fileList.map(item => item.url)
}
}
if (id) {
// ?draft=false:表示不是草稿状态,是直接“发布”的。
await http.put(`/mp/articles/${id}?draft=false`, params)
} else {
await http.post('/mp/articles?draft=false', params)
}
/*
这段逻辑是用来实现 “一页双用” 的:
没有 id 就走新增文章接口;
有 id 就走更新文章接口。
让发布和编辑共用同一个页面,效率高又方便管理。
params:是你前面收集好的表单数据,结构大致是这样
{
channel_id,
content,
title,
type,
cover: {
type,
images: [...]
}
}
*/
// 跳转列表 提示用户
navigate('/layout/article')
message.success(`${id ? '更新成功' : '发布成功'}`)
}
就是把 fileList
里的每一项的 url
提取出来,组成一个图片地址数组:
images: [
"http://geek.itheima.net/files/cover1.jpg",
"http://geek.itheima.net/files/cover2.jpg"
]
这些数据会一起发送给后端,保存为文章封面图。
二、如果想切换一图或者三图的功能
const radioChange = (e) =>{
const rawValue = e.target.value
setImgCount(rawValue)
console.log(cacheImgList.current);
// 从仓库里面获取对应的图片数量 交给用来渲染图片的fileList
if(cacheImgList.current === undefined || 0){
return false
}
if( rawValue === 1 ){
const img = cacheImgList.current ? cacheImgList.current[0] : []
setFileList([img])
}else if ( rawValue === 3 ){
setFileList(cacheImgList.current)
}
}
.....
<Item label="封面">
<Item name="type">
<Radio.Group onChange={radioChange}>
<Radio value={1}>单图</Radio>
<Radio value={3}>三图</Radio>
<Radio value={0}>无图</Radio>
</Radio.Group>
</Item>
{ imgCount > 0 && (
<Upload name='image' listType='picture-card' className='avatar-uploader' showUploadList action="http://geek.itheima.net/v1_0/upload" fileList={fileList} onChange={onUploadChange} multiple={ imgCount > 1 } maxCount={ imgCount } >
<div style={{marginTop:8}}>
<PlusOutlined />
</div>
</Upload>)
}
</Item>
cacheImgList
是干什么的?
-
fileList
是正在显示的图片列表,用于<Upload />
显示当前上传的图片。 -
cacheImgList.current
是缓存所有上传过的图片,不管当前是否显示。
比如你上传了 3 张图,然后你选择“单图” → 只显示 1 张
再切回“三图” → 你希望还能看到之前上传的 3 张图!
所以我们在上传时就缓存一份完整列表,以便随时恢复。
举个真实例子
用户流程如下:
-
选择 “三图”
-
上传了 3 张图
👉 此时:
fileList = [图1, 图2, 图3]
cacheImgList.current = [图1, 图2, 图3]
-
用户切换到 “单图” 👉 代码只显示
fileList = [图1]
-
用户切回 “三图” 👉 从
cacheImgList.current
恢复出[图1, 图2, 图3]
如果你不缓存,之前上传的图就“丢了”,只能重新上传,非常影响体验。
为什么用 useRef
?
因为我们不需要触发组件重新渲染,仅仅是临时存储一些数据,这时候就推荐用 useRef
:
const cacheImgList = useRef() // 相当于一个组件内的变量
比用 useState
更轻量,也不会引起重复渲染。
cacheImgList.current = formatList
作用:在图片上传成功后,把完整的图片列表存一份“缓存”,用于后续切换封面图数量时做“图像恢复”。