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

如何阻止React在值变更时重新渲染动态表单组件?

问题描述

我正在实现基于下拉选项生成动态表单字段的功能,UI表现如下:

  • 初始状态:显示基础表单,包含名称输入框、一行字段配置(字段名称、数据类型下拉、约束多选下拉),以及「添加」「保存」按钮
  • 选中单个约束选项:对应生成一个输入字段用于填写约束值
  • 选中多个约束选项:对应生成多个输入字段,每个约束对应一个输入框

核心问题:修改动态生成的约束字段值时,React组件重新渲染会导致字段值被清空。虽然State中已更新值(比如Min设为2、Max设为3),但UI无法显示最新值。单选约束时无此问题,值能正常保存显示;多选约束时则会出现值重置的情况。


补充代码

渲染代码

render(){
    let {testDataSpec} = this.state;
    const renderConstraints = (constraints, index) => {
        if(constraints.length === 1){
            return <Input 
                label_val={constraints[0][0]}
                className={this.hasError("field") ? "is-invalid" : ""}
                type={'text'}
                name={constraints[0]+'Value'}
                handleChange={e => this.handleFormChange(index, 0, e)}
                errorDiv={this.hasError("field") ? "text-danger" : "d-none"}
                errorMsg={"Enter Constraint Value"}
            />
        }else if(constraints.length && constraints.length !== 1){
            return constraints.map((constraint, idx)=>(
                <Input 
                    label_val={constraints[idx][0]}
                    className={this.hasError("field") ? "is-invalid" : ""}
                    type={'text'}
                    name={constraints[idx]+'Value'}
                    handleChange={e => this.handleFormChange(index, idx, e)}
                    errorDiv={this.hasError("field") ? "text-danger" : "d-none"}
                    errorMsg={"Enter Constraint Value"}
                    key={constraints[idx]+'_' + idx}
                />
            ))
        }
    }
    return (
        <Fragment>
            <h2>Add Test Data Specification</h2>
            <hr />
            <form onSubmit={this.handleSubmit}>
                <input type="hidden" name="id" id="id" value={testDataSpec.id} onChange={this.handleChange} />
                <div className="row">
                    <Input 
                        label_val={"Name"}
                        className={this.hasError("name") ? "is-invalid" : ""}
                        type={'text'}
                        name={'name'}
                        handleChange={this.handleChange}
                        errorDiv={this.hasError("name") ? "text-danger" : "d-none"}
                        errorMsg={"Please enter TDS name"}
                    />
                </div>
                {this.state.formValues.map((element, index)=>(
                    <div key={index} className="row">
                        <div className="col-lg">
                            <Input 
                                label_val={"Field Name"}
                                className={this.hasError("field") ? "is-invalid" : ""}
                                type={'text'}
                                name={'field'}
                                handleChange={e => this.handleFormChange(index, e)}
                                errorDiv={this.hasError("field") ? "text-danger" : "d-none"}
                                errorMsg={"Please enter field name"}
                            />
                        </div>
                        <div className="col-lg">
                            <Select 
                                label_name={"DataTypes"}
                                name={"dtype"}
                                options={this.state.dataTypes}
                                handleChange={e => this.handleFormChange(index,  e)}
                            />
                        </div>
                        <div className="col-lg">
                            <Select 
                                label_name={"Constraints"}
                                name={"constraints"}
                                options={this.state.constraints}
                                handleChange={e => this.handleFormChange(index, null, e)}
                                isMultiple={true}
                            />
                        </div>
                        <div className="col-lg">
                            {
                                renderConstraints(element.constraints, index)
                            }
                        </div>
                        <div className="col-sm">
                            {
                                index ?
                                    <button style={{height: "50px", width: "100px", marginTop: "30px"}} type="button" className="btn btn-danger" onClick={() => this.removeFormFields(index)}>Remove</button>
                                :null
                            }
                        </div>
                    </div>
                ))}
                <hr />
                <button style={{height: "50px", width: "100px", marginRight: "20px"}} className="btn btn-success" type="button" onClick={() => this.addFormFields()}>Add</button>
                <button style={{height: "50px", width: "100px"}} className="btn btn-primary">Save</button>
            </form>
            <div className="mt-3">
                <pre>{JSON.stringify(this.state, null, 3)}</pre>
            </div>
        </Fragment>
    )
}

构造函数与事件处理代码

constructor(props){
    super(props);
    this.state = {
        testDataSpec: {
            id: 0,
            name: "",
        },
        formValues: [{field: "", dtype: "", constraints: {}}],
        isLoaded: false,
        error:null,
        errors: [],
        constraints: [
            {"id": "Min",         value: "Min"},
            {"id": "Max",         value: "Max"},
            {"id": "Range",       value: "Range"},
        ],
        dataTypes: [
            {"id": "short", value: "Int16"},
            {"id": "integer", value: "Int32"},
            {"id": "long", value: "Int64"},
            {"id": "float", value: "Float32"},
            {"id": "double", value: "Float64"},
            {"id": "string", value: "String"},
        ]
    }
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
}

hasError(key){
    return this.state.errors.indexOf(key) !== -1;
}

addFormFields(){
    this.setState(({
        formValues: [...this.state.formValues, {field: "", constraints: []}]
    }))
}

removeFormFields(i){
    let formValues = this.state.formValues;
    formValues.splice(i, 1);
    this.setState({formValues});
}

handleFormChange(i, c, e) {
    let formValues = this.state.formValues;
    if (e.target.name === "constraints"){
        formValues[i][e.target.name] = Array.from(e.target.selectedOptions, option => [option.value, null]);
    }else if(e.target.name.includes("Value")){
        console.log("(i, c) =  (", i, c, ")")
        let obj = [
            formValues[i]["constraints"][c][0],
            e.target.value
        ]
        formValues[i]["constraints"][c] = obj;
    }else{
        formValues[i][e.target.name] = e.target.value;
    }
    this.setState((prevState) => ({
        ...prevState,
        ["formValues"]: formValues,
    }))
}

解决方案

问题根源有两个:

  1. 动态输入框未绑定value属性:渲染约束输入框时,没有将State中保存的约束值与输入框绑定,导致重新渲染时输入框回到无值状态。
  2. 直接修改原State数组:handleFormChange和removeFormFields中直接修改了this.state.formValues的原数组,违反React不可变更新原则,导致State更新不被正确识别,引发渲染异常。

具体修复步骤

1. 给约束输入框添加value绑定

修改renderConstraints函数中的Input组件,添加value属性绑定State中的约束值:

const renderConstraints = (constraints, index) => {
    if(constraints.length === 1){
        return <Input 
            label_val={constraints[0][0]}
            className={this.hasError("field") ? "is-invalid" : ""}
            type={'text'}
            name={constraints[0]+'Value'}
            handleChange={e => this.handleFormChange(index, 0, e)}
            errorDiv={this.hasError("field") ? "text-danger" : "d-none"}
            errorMsg={"Enter Constraint Value"}
            value={constraints[0][1] || ""} // 新增:绑定约束值
        />
    }else if(constraints.length && constraints.length !== 1){
        return constraints.map((constraint, idx)=>(
            <Input 
                label_val={constraints[idx][0]}
                className={this.hasError("field") ? "is-invalid" : ""}
                type={'text'}
                name={constraints[idx]+'Value'}
                handleChange={e => this.handleFormChange(index, idx, e)}
                errorDiv={this.hasError("field") ? "text-danger" : "d-none"}
                errorMsg={"Enter Constraint Value"}
                key={constraints[idx]+'_' + idx}
                value={constraints[idx][1] || ""} // 新增:绑定约束值
            />
        ))
    }
}

2. 修复State的不可变更新

  • 修改removeFormFields,通过过滤创建新数组,避免直接修改原数组:
removeFormFields(i){
    this.setState(prevState => ({
        formValues: prevState.formValues.filter((_, idx) => idx !== i)
    }));
}
  • 修改handleFormChange,每次更新时创建新数组/对象,遵循不可变原则:
handleFormChange(i, c, e) {
    this.setState(prevState => {
        // 复制formValues数组
        const newFormValues = [...prevState.formValues];
        // 复制当前要修改的元素
        const updatedElement = {...newFormValues[i]};

        if (e.target.name === "constraints"){
            updatedElement[e.target.name] = Array.from(e.target.selectedOptions, option => [option.value, null]);
        }else if(e.target.name.includes("Value")){
            // 复制constraints数组
            const newConstraints = [...updatedElement.constraints];
            newConstraints[c] = [
                newConstraints[c][0],
                e.target.value
            ];
            updatedElement.constraints = newConstraints;
        }else{
            updatedElement[e.target.name] = e.target.value;
        }

        newFormValues[i] = updatedElement;
        return {
            ...prevState,
            formValues: newFormValues
        };
    })
}

3. 修正初始State的类型不一致

构造函数中formValues初始值的constraints是对象类型,而addFormFields中新增的是数组类型,统一改为数组:

formValues: [{field: "", dtype: "", constraints: []}],

修改完成后,动态输入框会正确绑定State值,同时State更新遵循不可变原则,组件重新渲染时就能正常显示最新输入值。

内容的提问来源于stack exchange,提问作者Stefan Radonjic

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 08:06:29