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

React组件意外修改props.data,需保留初始值对比控制提交按钮

React表单修改输入时意外修改props.data的问题解决及初始值对比禁用提交按钮实现

问题说明

实现了一个包含n个输入框的React Form组件,通过父组件传入data数组作为输入框初始值。当前遇到两个核心问题:

  1. 修改输入框值时,父组件传入的props.data被意外修改;
  2. 无法正确实现「表单值与初始值完全相同时禁用SubmitButton」的功能。

问题根源

  • 引用类型共享:数组是引用类型,直接将props.data赋值给state或在handleChange中直接修改数组,会导致原props.data的引用被篡改;
  • 非法setState触发无限重渲染:组件函数体中直接判断并调用setInputs(props.data!),每次渲染都会触发state更新,形成循环;
  • 初始值未持久保存:没有单独保留初始值的副本,无法准确对比当前值与初始值的差异。

解决方案及修改代码

核心修改点

  1. 拷贝数组切断引用:所有数组操作都创建新副本,避免修改原引用;
  2. 用useRef持久保存初始值:ref不会触发重渲染,能始终保留初始值用于对比;
  3. 用useEffect替代函数体中的setState:仅在props.data变化时同步state,避免无限循环;
  4. 实现初始值对比逻辑:通过监听inputs变化,结合「字段完整性」和「与初始值是否一致」两个条件控制按钮禁用状态;
  5. 将OneInput改为受控组件:用value绑定state值,确保输入框与state同步。

修改后的Form组件

import { useState, useEffect, useRef } from "react";

interface Props {
    itemsNum: number;
    types?: string[];
    labels?: string[];
    submitText?: string;
    disableOnClick?: boolean;
    enableOnInput?: boolean;
    trigger?: number;
    data?: any[];

    onSubmit(inputs: any[]): any;
    fetchData?(): any[] | Promise<any[]>;
}

// 数组全等判断函数(若已有可替换)
function equalsArr(arr1: any[], arr2: any[]): boolean {
    if (arr1.length !== arr2.length) return false;
    for (let i = 0; i < arr1.length; i++) {
        if (arr1[i] !== arr2[i]) return false;
    }
    return true;
}

export function Form(props: Props) {
    // 初始化时拷贝props.data,切断引用关联
    const [inputs, setInputs] = useState<any[]>(props.data ? [...props.data] : []);
    const [disabledInputs, setDisabledInputs] = useState<boolean>(false);
    const [disabledSubmit, setDisabledSubmit] = useState<boolean>(true);
    // 用ref持久保存初始值
    const initialDataRef = useRef<any[]>(props.data ? [...props.data] : []);

    // 监听props.data变化,同步state并更新初始值
    useEffect(() => {
        if (props.data) {
            const newData = [...props.data];
            setInputs(newData);
            initialDataRef.current = newData;
        }
    }, [props.data]);

    // 监听inputs变化,自动更新提交按钮禁用状态
    useEffect(() => {
        const hasUndefined = hasUndefinedFields(inputs);
        const isSameAsInitial = equalsArr(inputs, initialDataRef.current);
        // 禁用条件:存在未定义字段 或 与初始值完全一致
        setDisabledSubmit(hasUndefined || isSameAsInitial);
    }, [inputs]);

    function handleChange(e: any, i: number) {
        const { value } = e.target;
        // 创建新数组副本,避免修改原引用
        const newArr = [...inputs];
        newArr[i] = value;
        setInputs(newArr);
    }

    function hasUndefinedFields(arr: any[]): boolean {
        for (const item of arr) {
            if (!item) return true;
        }
        return false;
    }

    function validateInputs(): boolean {
        for (const input of inputs) {
            if (!input || String(input).match(/^0+$/)) { // 替换为你的Regex.ZEROS逻辑
                return false;
            }
        }
        return true;
    }

    function validateArrLen(): boolean {
        return props.itemsNum >= 0;
    }

    function getInputs() {
        let inps = [];
        for (let i = 0; i < props.itemsNum; i++) {
            inps.push(
                <OneInput
                    key={i}
                    label={props.labels?.length && props.labels[i] ? props.labels[i] : ''}
                    type={(props.types?.length && props.types[i]) ? props.types[i] : 'text'}
                    name={`input${i + 1}`}
                    onChange={(e: any) => handleChange(e, i)}
                    value={inputs[i] ?? ''} // 改为受控组件,绑定value
                    disabled={disabledInputs}
                />
            );
        }
        return inps;
    }

    const content = validateArrLen() ? (
        <>
            {getInputs()}
            <SubmitButton
                text={props.submitText ? props.submitText : '保存'} // 替换为你的Strings.SAVE常量
                disabled={disabledSubmit}
                onSubmit={(e: any) => {
                    e.preventDefault();
                    if (validateInputs()) {
                        props.onSubmit(inputs);
                        setDisabledInputs(props.disableOnClick ?? true);
                        setDisabledSubmit(props.disableOnClick ?? true);
                    } else {
                        alert('存在无效字段'); // 替换为你的Errors.NULL_FILEDS_ERROR常量
                    }
                }}
            />
        </>
    ) : (
        <div>错误:属性长度冲突</div>
    );

    return content;
}

修改后的OneInput组件

interface OneInputProps {
    label?: string;
    name: string;
    type?: string;
    onChange: any;
    value?: string | number | undefined;
    disabled?: boolean;
}

export function OneInput(props: OneInputProps) {
    return (
        <label>
            {props.label}
            <input
                type={props.type || 'text'}
                name={props.name}
                onChange={props.onChange}
                value={props.value ?? ''} // 绑定value,实现受控组件
                disabled={props.disabled}
            />
        </label>
    );
}

内容的提问来源于stack exchange,提问作者M.Olaby

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 05:45:00