You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 09:22:03