React表单动态添加输入域及嵌套状态数组存储技术问询
React表单动态添加地点与图片上传实现
需求说明
- 为表单添加点击事件实现图片上传功能
- 点击「添加地点」按钮时,新增一组输入域:2个文件选择框(用于上传地点照片)+1个地址输入框,每组对应一个独立地点(仅允许两张照片+一个地址)
- 将所有输入值统一存储到嵌套在state对象的
photos数组中,数组每个元素对应一个地点信息
TypeScript类型定义
export interface Data { client: string; date: string; // 修正原接口拼写错误(原data改为date) photos: Place[]; } export interface Place { photo_1: string; // 存储图片base64字符串或文件路径 photo_2: string; address: string; }
完整实现代码
import { useState, ChangeEvent, FormEvent } from 'react'; // 导入上述TypeScript接口 const Form = () => { const [data, setData] = useState<Data>({ client: "", date: new Date().toISOString().split('T')[0], // 初始化当前日期为yyyy-MM-dd格式 photos: [ { photo_1: "", photo_2: "", address: "" } // 默认显示一组地点输入域 ], }); // 处理普通输入框变化(client、date、address) const handleTextChange = (e: ChangeEvent<HTMLInputElement>, placeIndex?: number) => { const { name, value } = e.target; if (placeIndex !== undefined) { // 更新指定地点的字段 setData(prev => ({ ...prev, photos: prev.photos.map((place, idx) => idx === placeIndex ? { ...place, [name]: value } : place ) })); } else { // 更新顶层字段(client、date) setData(prev => ({ ...prev, [name]: value })); } }; // 处理文件上传并转base64 const handleFileChange = (e: ChangeEvent<HTMLInputElement>, placeIndex: number, photoKey: 'photo_1' | 'photo_2') => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = () => { setData(prev => ({ ...prev, photos: prev.photos.map((place, idx) => idx === placeIndex ? { ...place, [photoKey]: reader.result as string } : place ) })); }; reader.readAsDataURL(file); }; // 添加新的地点输入组 const addNewPlace = () => { setData(prev => ({ ...prev, photos: [...prev.photos, { photo_1: "", photo_2: "", address: "" }] })); }; // 表单提交处理 const handleSubmit = (e: FormEvent) => { e.preventDefault(); // 这里可以添加提交逻辑,比如发送到后端 console.log('表单数据:', data); }; return ( <form onSubmit={handleSubmit}> <div className="inputs"> {/* 客户名称输入 */} <div> <label htmlFor="client">客户名称</label> <input id="client" type="text" name="client" value={data.client} onChange={(e) => handleTextChange(e)} /> </div> {/* 日期选择 */} <div> <label htmlFor="date">日期</label> <input id="date" type="date" name="date" value={data.date} onChange={(e) => handleTextChange(e)} /> </div> {/* 地点输入组 */} <div> <label>地点信息</label> {data.photos.map((place, index) => ( <div key={index} className="place-group"> <h4>地点 {index + 1}</h4> {/* 第一张照片上传 */} <div> <label>照片1</label> <input type="file" accept="image/*" onChange={(e) => handleFileChange(e, index, 'photo_1')} /> </div> {/* 第二张照片上传 */} <div> <label>照片2</label> <input type="file" accept="image/*" onChange={(e) => handleFileChange(e, index, 'photo_2')} /> </div> {/* 地址输入 */} <div> <label htmlFor={`address-${index}`}>地址</label> <input id={`address-${index}`} type="text" name="address" value={place.address} onChange={(e) => handleTextChange(e, index)} /> </div> </div> ))} {/* 添加地点按钮 */} <button type="button" onClick={addNewPlace}> 添加地点 </button> </div> {/* 提交按钮 */} <button type="submit">提交表单</button> </div> </form> ); }; export default Form;
关键实现说明
- 动态渲染地点组:通过
data.photos.map遍历数组,每个地点组绑定对应的索引,确保更新时能定位到正确的数组元素 - 文件处理逻辑:使用
FileReader将上传的图片转换为base64字符串,符合Place接口中字符串类型的要求,也方便前端预览或后端存储 - 状态更新方式:针对顶层字段(client、date)和嵌套数组字段(photos中的元素)分别处理,使用函数式更新确保状态的不可变性
- 按钮类型区分:「添加地点」按钮设置
type="button",避免触发表单默认提交行为
内容的提问来源于stack exchange,提问作者mongu
相关产品推荐
相关产品推荐

