如何阻止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, })) }
解决方案
问题根源有两个:
- 动态输入框未绑定
value属性:渲染约束输入框时,没有将State中保存的约束值与输入框绑定,导致重新渲染时输入框回到无值状态。 - 直接修改原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
相关产品推荐
相关产品推荐

