React中数组合并失败求助:一个来自props参数,一个来自state
图片上传数组合并失败问题解决
问题描述
实现图片上传功能时,子组件可通过props将上传的图片传递至父组件,但父组件无法将state中的现有图片数组与子组件传来的新图片数组合并。尝试过扩展运算符(...)、concat()方法及直接修改state等方式,均无效。
父组件(EntryEditPage)代码
const EntryEditPage = () => { const [currentImages, setCurrentImages] = useState([]); const handleImagesUpload = (uploadedImages) => { console.log('handleImagesUpload -- curIm:', currentImages); // 输出:正确的现有图片 console.log('handleImagesUpload -- uplIm:', uploadedImages); // 输出:实际为空数组 console.log('handleImagesUpload -- expected result:', [ ...currentImages, ...uploadedImages, ]); // 输出:[] setCurrentImages((prevState) => [...prevState, ...uploadedImages]); }; return ( <section> <ImageUpload currentImages={currentImages} handleImagesUpload={(images) => handleImagesUpload(images)} /> <section> {currentImages.map((image) => ( <div key={image.name} > <img src={image.url} alt={image.name} /> </div> ))} </section> </section> ) } export default EntryEditPage;
子组件(ImageUpload)代码
const ImageUpload = ({ currentImages, handleImagesUpload }) => { const [uploadedImages, setUploadedImages] = useState([]); const [isErrorFilesLimit, setIsErrorFilesLimit] = useState(false); const [errorFileName, setErrorFileName] = useState(''); const [errorFileType, setErrorFileType] = useState(''); const [errorFileSize, setErrorFileSize] = useState(''); const allowedFileExtensions = /\.(jpe?g|png)$/i; const maxFiles = 10; const maxFileSize = 10485760; // 10MB per image const { register } = useFormContext(); const addError = (errors, newError) => `${errors}${errors ? '\n' : ''}${newError}`; const handleChange = (event) => { const validUploadedImages = []; let currentErrorsFileName = ''; let currentErrorsFileType = ''; let currentErrorsFileSize = ''; [...event.target.files].every((file) => { // 检查文件数量限制 if ([...currentImages, ...validUploadedImages].length >= maxFiles) { setIsErrorFilesLimit(true); return false; } // 检查文件名是否重复 if ( [...currentImages, ...validUploadedImages].findIndex( (f) => f.name === file.name ) >= 0 ) { currentErrorsFileName = addError(currentErrorsFileName, file.name); return true; } // 检查文件类型 if (!file.name.match(allowedFileExtensions)) { currentErrorsFileType = addError(currentErrorsFileType, file.name); return true; } // 检查文件大小 if (file.size > maxFileSize) { currentErrorsFileSize = addError(currentErrorsFileSize, file.name); return true; } // 文件校验通过,读取文件 const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => { const constructedFile = { name: file.name, url: reader.result, file, }; validUploadedImages.push(constructedFile); }; return true; }); !currentErrorsFileName || setErrorFileName(currentErrorsFileName); !currentErrorsFileType || setErrorFileType(currentErrorsFileType); !currentErrorsFileSize || setErrorFileSize(currentErrorsFileSize); if (validUploadedImages.length > 0) { setUploadedImages(validUploadedImages); } console.log(validUploadedImages); // 控制台看似有值,但实际是异步更新后的结果 handleImagesUpload(validUploadedImages); }; return ( <div> <section> <p className={styles.dropzoneAreaSubtext}> Supported formats: JPEG and PNG </p> <input {...register('images')} id="image-input" multiple type="file" accept="image/jpeg, image/png" title="Upload image" onChange={handleChange} className={styles.dropzoneAreaInput} /> </section> <div className={styles.dropzoneSubtitleWrapper}> {uploadedImages.length > 0 && ( <p className={styles.dropzoneSubtitle}> Uploaded {uploadedImages.length} photos </p> )} {isErrorFilesLimit && ( <p className={styles.dropzoneError} data-testid="filesLimitExceeded"> <span className={styles.dropzoneErrorHeadline}> You reached the limit of {maxFiles} photos. Remove a photo to upload a new one. </span> </p> )} {errorFileName && ( <p className={styles.dropzoneError}> <span className={styles.dropzoneErrorHeadline}> Image file name already exists: </span> <br /> {errorFileName} </p> )} {errorFileType && ( <p className={styles.dropzoneError}> <span className={styles.dropzoneErrorHeadline}> Unsupported format: </span> <br /> {errorFileType} </p> )} {errorFileSize && ( <p className={styles.dropzoneError}> <span className={styles.dropzoneErrorHeadline}> Maximum file size (10MB) exceeded: </span> <br /> {errorFileSize} </p> )} </div> </div> ); }; export default ImageUpload;
问题根源
核心问题在于**FileReader.onload是异步操作**:在handleChange函数中,调用handleImagesUpload(validUploadedImages)时,所有reader.onload还未执行完毕,此时validUploadedImages仍是空数组。控制台中看到的数组有值,是因为控制台会动态更新引用类型的内容,并非调用函数时的真实状态。
解决方案
将文件读取逻辑改为异步等待,确保所有文件读取完成后再传递数组给父组件。可以用Promise.all处理所有文件的读取操作:
修改子组件的handleChange函数如下:
const handleChange = async (event) => { const validFilePromises = []; let currentErrorsFileName = ''; let currentErrorsFileType = ''; let currentErrorsFileSize = ''; let filesLimitExceeded = false; // 先同步校验所有文件,收集合法文件的读取Promise [...event.target.files].forEach((file) => { if (filesLimitExceeded) return; // 检查文件数量限制 if ([...currentImages, ...validFilePromises].length >= maxFiles) { setIsErrorFilesLimit(true); filesLimitExceeded = true; return; } // 检查文件名是否重复 if (currentImages.findIndex(f => f.name === file.name) >= 0) { currentErrorsFileName = addError(currentErrorsFileName, file.name); return; } // 检查文件类型 if (!file.name.match(allowedFileExtensions)) { currentErrorsFileType = addError(currentErrorsFileType, file.name); return; } // 检查文件大小 if (file.size > maxFileSize) { currentErrorsFileSize = addError(currentErrorsFileSize, file.name); return; } // 为合法文件创建读取Promise const readFilePromise = new Promise((resolve) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => { resolve({ name: file.name, url: reader.result, file, }); }; }); validFilePromises.push(readFilePromise); }); // 更新错误状态 setIsErrorFilesLimit(filesLimitExceeded); setErrorFileName(currentErrorsFileName); setErrorFileType(currentErrorsFileType); setErrorFileSize(currentErrorsFileSize); if (validFilePromises.length === 0) return; // 等待所有文件读取完成 const validUploadedImages = await Promise.all(validFilePromises); // 更新子组件状态并通知父组件 setUploadedImages(validUploadedImages); handleImagesUpload(validUploadedImages); };
额外优化点
- 父组件中
setCurrentImages((prevState) => [...prevState, ...uploadedImages])的写法是正确的,使用函数式更新能确保基于最新的state进行修改,无需调整。 - 移除了原代码中多余的
/>(父组件map里的img标签后)。
内容的提问来源于stack exchange,提问作者Katrin
相关产品推荐
相关产品推荐

