Next.js中react-sortablejs结合本地图片src不显示问题求助
在Next.js项目里用react-sortablejs组件处理本地图片,排序后图片的src路径失效,控制台报错Image is missing required "src" property:。相关代码如下:
import { useState } from "react"; import axios from "axios"; import { useRouter } from "next/router"; import Image from "next/image"; import Spinner from "./Spinner"; import { ReactSortable } from "react-sortablejs"; export default function ProductForm({ _id, title: existingTitle, description: existingDescription, price: existingPrice, images: existingImages, }) { const [title, setTitle] = useState(existingTitle || ""); const [description, setDescription] = useState(existingDescription || ""); const [price, setPrice] = useState(existingPrice || ""); const [images, setImages] = useState(existingImages || []); const [goToProducts, setGoToProducts] = useState(""); const [isUploading, setIsUploading] = useState(false); const router = useRouter(); async function saveProduct(ev) { ev.preventDefault(); const data = { title, description, price, images }; if (_id) { await axios.put("/api/products", { ...data, _id }); } else { await axios.post("/api/products", data); } setGoToProducts(true); } if (goToProducts) { router.push("/products"); } async function uploadImages(ev) { const files = ev.target?.files; if (files?.length > 0) { setIsUploading(true); const data = new FormData(); for (const file of files) { data.append("file", file); } const res = await axios.post("/api/upload", data); for (let i = 0; i < res.data.path.length; i++) { console.log(res.data.path[i]); } setImages((OldImages) => { return [...OldImages, ...res.data.path]; }); setIsUploading(false); } } function updateImagesOrder(images) { setImages(images); } return ( <form onSubmit={saveProduct}> <label>Nazwa Produkt</label> <input type="text" placeholder="Nazwa Produktu" value={title} onChange={(ev) => setTitle(ev.target.value)} /> <label>Opis Produktu</label> <textarea placeholder="Opis" value={description} onChange={(ev) => setDescription(ev.target.value)} ></textarea> <label>Zdjęcia Produktu</label> <div className="mb-2 flex flex-wrap gap-1"> <ReactSortable list={images} className="flex flex-wrap gap-1" setList={updateImagesOrder} > {images?.length && images.map((path) => ( <div key={path}> <Image src={path} alt="product_image" width={0} height={0} sizes="6rem" style={{ width: "100%", height: "100px" }} className="rounded-lg" /> </div> ))} </ReactSortable> {isUploading && ( <div className="h-24 flex items-center"> <Spinner /> </div> )} <label className="w-24 h-24 cursor-pointer text-center flex items-center justify-center text-sm gap-1 text-gray-500 rounded-lg bg-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6" > <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5" /> </svg> <div>Wgraj</div> <input type="file" onChange={uploadImages} className="hidden" multiple ></input> </label> </div> <label>Cena Produktu</label> <input type="number" placeholder="Cena" value={price} onChange={(ev) => setPrice(ev.target.value)} /> <button type="submit" className="btn-primary"> Zapisz Produkt </button> </form> ); }
调试发现:当updateImagesOrder仅执行console.log(images)时,图片显示正常,控制台输出的数组元素是带额外属性的字符串:
(2) [String, String] 0 : String {'/2024-02-07T15-43-47.272Z-Fundamental.png', chosen: false, selected: false} 1 : String {'/2024-02-07T15-43-47.260Z-css-flexbox-poster.png', chosen: false, selected: false} length : 2
但改成setImages(images)后,图片无法显示,触发src缺失的错误。
问题根源是react-sortablejs会给数组中的每个元素添加额外的属性(比如chosen、selected),原本的字符串会被包装成一个带这些属性的类字符串对象。当你把这个修改后的数组设置回state时,Image组件的src接收的不再是纯字符串,而是一个对象,导致Next.js无法识别路径。
解决步骤:
在更新state时,提取纯字符串路径
修改updateImagesOrder函数,把每个元素转换回纯字符串:function updateImagesOrder(images) { // 提取每个元素的原始字符串值,去掉sortable添加的额外属性 const pureImagePaths = images.map(img => String(img)); setImages(pureImagePaths); }优化key值(可选但推荐)
因为排序后数组元素的位置变化,用path做key是安全的,但如果有重复路径的情况,可以结合索引或者添加唯一标识。不过如果你的图片路径都是唯一的,当前的key={path}没问题。验证上传逻辑(补充)
确保上传接口返回的res.data.path是纯字符串数组,而不是其他格式。当前代码里的setImages((OldImages) => [...OldImages, ...res.data.path])是正确的,因为上传的路径是纯字符串。
这样修改后,排序后的数组里就只有纯字符串路径,Image组件的src就能正常识别,图片可以显示同时排序功能也能正常工作。
内容的提问来源于stack exchange,提问作者revoxer

