跨组件重置文件输入Refs以支持文件重新上传
问题与优化需求
我目前只找到一些不规范的解决方案,希望能得到更优的思路。需求如下:
- UploadComponent 通过 refs 实现用户文件选择
- 文件选中后传递至父组件 App,由 App 跟踪已上传文件
- FilePreviewComponent 展示已上传文件并支持删除操作,删除后需重置 UploadComponent 中的文件输入 refs,以允许重新上传同一文件
目前尚未找到跨组件重置 refs 的可行方案,以下是现有代码示例:
App 组件代码
const App = () => { const { control, handleSubmit, setValue, getValues } = useForm(); const [selectedFiles, setSelectedFiles] = useState([]); const onSubmit = (data) => { console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> {/* Upload Component */} <Controller name="files" control={control} render={({ field: { value, onChange } }) => ( <UploadComponent onFileSelected={(files) => { const updatedFiles = [...value, ...files]; onChange(updatedFiles); setSelectedFiles(updatedFiles); }} /> )} /> {/* File Preview Component */} <Controller name="files" control={control} render={({ field: { value } }) => ( <FilePreviewComponent files={value} onDelete={(fileToDelete) => { const updatedFiles = value.filter((file) => file.uri !== fileToDelete.uri); setSelectedFiles(updatedFiles); setValue("files", updatedFiles); }} /> )} /> <button type="submit">Submit</button> </form> ); }; export default App;
UploadComponent 代码
const UploadComponent = ({ onFileSelected }) => { const fileInputRef = useRef(null); const handleFileUpload = (e) => { const files = Array.from(e.target.files).map((file) => ({ uri: URL.createObjectURL(file), // Create a temporary URL for preview name: file.name, type: file.type, blob: file, })); onFileSelected(files); }; return ( <div> {/* Button to trigger file input */} <button onClick={() => fileInputRef.current.click()}>Upload File</button> {/* Hidden file input triggered by button */} <input ref={fileInputRef} type="file" accept="image/*,application/pdf" style={{ display: "none" }} onChange={handleFileUpload} /> </div> ); }; export default UploadComponent;
优化解决方案
核心思路是将重置文件输入的逻辑封装在 UploadComponent 内部,通过父组件传递状态触发重置,遵循 React 单向数据流原则,避免跨组件直接操作 refs:
1. 修改 UploadComponent,添加状态触发的重置逻辑
在组件中监听父组件传递的重置触发状态,状态变化时清空文件输入框的值:
import { useRef, useEffect } from 'react'; const UploadComponent = ({ onFileSelected, resetTrigger }) => { const fileInputRef = useRef(null); const handleFileUpload = (e) => { const files = Array.from(e.target.files).map((file) => ({ uri: URL.createObjectURL(file), name: file.name, type: file.type, blob: file, })); onFileSelected(files); }; // 监听重置触发信号,更新时清空文件输入 useEffect(() => { if (resetTrigger && fileInputRef.current) { fileInputRef.current.value = ''; } }, [resetTrigger]); return ( <div> <button onClick={() => fileInputRef.current.click()}>Upload File</button> <input ref={fileInputRef} type="file" accept="image/*,application/pdf" style={{ display: "none" }} onChange={handleFileUpload} /> </div> ); }; export default UploadComponent;
2. 修改 App 组件,添加重置触发状态并关联删除操作
在 App 中维护一个布尔类型的重置触发状态,删除文件时切换状态值,触发 UploadComponent 的重置逻辑:
import { useState } from 'react'; import { useForm, Controller } from 'react-hook-form'; import UploadComponent from './UploadComponent'; import FilePreviewComponent from './FilePreviewComponent'; const App = () => { const { control, handleSubmit, setValue } = useForm(); const [selectedFiles, setSelectedFiles] = useState([]); const [resetUploadTrigger, setResetUploadTrigger] = useState(false); const onSubmit = (data) => { console.log(data); }; const handleDelete = (fileToDelete) => { const updatedFiles = selectedFiles.filter((file) => file.uri !== fileToDelete.uri); setSelectedFiles(updatedFiles); setValue("files", updatedFiles); // 触发重置:通过状态切换触发子组件的useEffect setResetUploadTrigger(true); setTimeout(() => setResetUploadTrigger(false), 0); }; return ( <form onSubmit={handleSubmit(onSubmit)}> {/* Upload Component */} <Controller name="files" control={control} render={({ field: { value, onChange } }) => ( <UploadComponent onFileSelected={(files) => { const updatedFiles = [...value, ...files]; onChange(updatedFiles); setSelectedFiles(updatedFiles); }} resetTrigger={resetUploadTrigger} /> )} /> {/* File Preview Component */} <Controller name="files" control={control} render={({ field: { value } }) => ( <FilePreviewComponent files={value} onDelete={handleDelete} /> )} /> <button type="submit">Submit</button> </form> ); }; export default App;
方案优势
- 遵循 React 单向数据流,代码结构清晰、易维护
- 重置逻辑封装在 UploadComponent 内部,组件职责单一
- 状态触发的方式稳定可靠,不会出现 refs 未挂载的异常
内容的提问来源于stack exchange,提问作者Katharina Schreiber
相关产品推荐
相关产品推荐

