React中如何从同构子组件表单创建对象数组
实现重复子表单收集结构化联系人数组的React方案
看起来你想要通过重复渲染的子表单组件,收集数据并整合成指定格式的联系人对象数组。我来帮你优化现有代码,让它更符合React的数据流模式,同时完美满足你的需求:
先梳理下现有代码的小问题
- 子组件里给表单字段的
name加了formNo后缀(比如salutation1),这会导致最终收集的对象字段名不符合你想要的格式(你需要的是salutation而不是salutation1) - 父组件目前只是单个打印子组件传递的数据,没有把两个联系人的数据整合到一个数组里
- 用循环直接赋值
useRef的方式不太安全,React不推荐在循环里直接操作ref
优化后的完整实现
1. 父组件:统一管理联系人状态数组
父组件用useState初始化一个包含两个空对象的数组,用来存储两个联系人的数据。给每个子组件传递索引和更新方法,让子组件的输入变化直接同步到父组件的状态中:
import React, { useState } from 'react'; import { Grid, FormLabel, Button } from '@mui/material'; import DeliveryContact from './DeliveryContact'; const ParentComponent = () => { // 初始化两个空联系人对象,对应两个子表单 const [contacts, setContacts] = useState([{}, {}]); // 更新对应索引的联系人数据 const updateContact = (index, updatedFields) => { setContacts(prevContacts => { const newContacts = [...prevContacts]; // 合并已有数据和新输入的字段 newContacts[index] = { ...newContacts[index], ...updatedFields }; return newContacts; }); }; // 提交时可以直接使用contacts数组,就是你想要的格式 const handleSubmit = () => { console.log("最终联系人数组:", contacts); // 这里可以把contacts传递给后端或者做其他处理 }; return ( <React.Fragment> {contacts.map((_, index) => ( <React.Fragment key={index}> <Grid item xs={12}> <FormLabel component="legend"> Delivery Contact Person : ({index + 1}) </FormLabel> </Grid> <DeliveryContact formNo={index + 1} contactData={contacts[index]} onUpdate={(fields) => updateContact(index, fields)} /> </React.Fragment> ))} <Grid item xs={12} style={{ marginTop: 20 }}> <Button variant="contained" onClick={handleSubmit}> 提交联系人信息 </Button> </Grid> </React.Fragment> ); }; export default ParentComponent;
2. 子组件:作为受控组件传递输入变化
子组件不再维护独立状态(或保留本地状态做临时存储),接收父组件的contactData作为当前值,输入变化时通过onUpdate把字段传递给父组件。同时去掉不必要的name后缀,保证字段名和期望的对象结构一致:
import React, { useState, useEffect } from 'react'; import { Grid, TextField, MenuItem } from '@mui/material'; // 定义默认的联系人字段结构 const defaultContact = { salutation: '', name: '', phone: '', email: '', designation: '' }; const DeliveryContact = ({ formNo, contactData, onUpdate }) => { // 子组件本地临时状态,也可以直接用contactData做纯受控组件 const [values, setValues] = useState(defaultContact); // 同步父组件传递的最新数据到本地 useEffect(() => { setValues(prev => ({ ...prev, ...contactData })); }, [contactData]); const handleInputChange = ({ target }) => { const { name, value } = target; const updatedValues = { ...values, [name]: value }; setValues(updatedValues); // 实时把更新的字段传给父组件 onUpdate({ [name]: value }); }; return ( <React.Fragment> <Grid item xs={12} sm={6}> <TextField select fullWidth margin="dense" id="salutation" name="salutation" // 保持原始字段名,不加后缀 onChange={handleInputChange} value={values.salutation} variant="outlined" label="Salutation" > <MenuItem value="Mr.">Mr.</MenuItem> <MenuItem value="Ms.">Ms.</MenuItem> </TextField> </Grid> <Grid item xs={12} sm={6}> <TextField id="deliveryContactName" name="name" // 对应期望对象的name字段 label="Delivery Contact Name" fullWidth variant="outlined" margin="dense" onChange={handleInputChange} value={values.name} inputProps={{ maxLength: 40 }} /> </Grid> {/* 补充剩余需要的字段 */} <Grid item xs={12} sm={6}> <TextField name="phone" label="Phone Number" fullWidth variant="outlined" margin="dense" onChange={handleInputChange} value={values.phone} type="number" /> </Grid> <Grid item xs={12} sm={6}> <TextField name="email" label="Email" fullWidth variant="outlined" margin="dense" onChange={handleInputChange} value={values.email} type="email" /> </Grid> <Grid item xs={12} sm={12}> <TextField name="designation" label="Designation" fullWidth variant="outlined" margin="dense" onChange={handleInputChange} value={values.designation} /> </Grid> </React.Fragment> ); }; export default DeliveryContact;
关键改进说明
- 统一状态管理:父组件的
contacts数组就是最终你需要的格式,随时可以使用,避免了子组件状态分散难以整合的问题 - 去掉字段后缀:保证收集的字段名和你期望的
name、salutation等一致,不会出现name1这种多余的后缀 - 替代ref调用:用回调函数传递变化,比
useImperativeHandle更符合React的单向数据流模式,代码更易维护 - 清晰的索引对应:每个子组件对应数组中的一个元素,不会出现数据混乱的情况
最终效果
当用户在两个子表单中输入数据后,父组件的contacts状态就会变成你想要的格式:
[ { "name": "asdasdas123da", "phone": 9898989898, "email": "test@mailinator.com", "designation": "Some Manager", "salutation": "Mr." }, { "name": "asdasdas", "phone": 9898989898, "email": "test@mailinator.com", "designation": "Some Manager", "salutation": "Mr." } ]
内容的提问来源于stack exchange,提问作者Deepak
相关产品推荐
相关产品推荐

