React函数式组件实现含Select、文本框、自适应文本域的动态行增删(MUI+Yup校验)
前置依赖安装
先安装实现所需的所有依赖包:
npm install @mui/material @emotion/react @emotion/styled react-hook-form @hookform/resolvers yup
完整实现代码
import React from 'react'; import { useForm, Controller, useFieldArray } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; import { TextField, Select, MenuItem, Button, Box, Typography, Grid } from '@mui/material'; // Yup校验规则定义 const formSchema = yup.object().shape({ rows: yup.array().of( yup.object().shape({ text1: yup.string().required('字段1不能为空'), text2: yup.string().required('字段2不能为空'), selectVal: yup.string().required('请选择下拉选项'), desc: yup.string().required('描述内容不能为空') }) ) }); function DynamicForm() { // 初始化表单实例,绑定Yup校验 const { control, handleSubmit, formState: { errors } } = useForm({ resolver: yupResolver(formSchema), defaultValues: { // 初始默认一行空数据 rows: [{ text1: '', text2: '', selectVal: '', desc: '' }] } }); // 动态行管理API,自动处理行的增删、状态同步 const { fields, append, remove, pop } = useFieldArray({ control, name: 'rows' }); // 表单提交逻辑 const handleFormSubmit = (formData) => { console.log('提交的完整表单数据:', formData); // 此处可添加接口提交逻辑 }; return ( <Box sx={{ maxWidth: 1200, margin: '2rem auto', padding: '0 1rem' }}> <Typography variant="h5" mb={3}>动态增删表单示例</Typography> <form onSubmit={handleSubmit(handleFormSubmit)}> {/* 遍历渲染所有表单行 */} {fields.map((row, index) => ( <Grid container spacing={2} key={row.id} alignItems="flex-start" mb={2}> <Grid item xs={0.5}> <Typography mt={1.5}>{index + 1}</Typography> </Grid> {/* 第一个文本输入框 */} <Grid item xs={2}> <Controller name={`rows.${index}.text1`} control={control} render={({ field }) => ( <TextField {...field} label="字段1" variant="outlined" fullWidth error={!!errors?.rows?.[index]?.text1} helperText={errors?.rows?.[index]?.text1?.message} /> )} /> </Grid> {/* 第二个文本输入框 */} <Grid item xs={2}> <Controller name={`rows.${index}.text2`} control={control} render={({ field }) => ( <TextField {...field} label="字段2" variant="outlined" fullWidth error={!!errors?.rows?.[index]?.text2} helperText={errors?.rows?.[index]?.text2?.message} /> )} /> </Grid> {/* 下拉选择框 */} <Grid item xs={2}> <Controller name={`rows.${index}.selectVal`} control={control} render={({ field }) => ( <Select {...field} displayEmpty fullWidth error={!!errors?.rows?.[index]?.selectVal} > <MenuItem value="" disabled>请选择</MenuItem> <MenuItem value="opt1">选项1</MenuItem> <MenuItem value="opt2">选项2</MenuItem> <MenuItem value="opt3">选项3</MenuItem> </Select> )} /> {errors?.rows?.[index]?.selectVal && ( <Typography variant="caption" color="error" ml={0.5}> {errors.rows[index].selectVal.message} </Typography> )} </Grid> {/* 自适应高度文本域 */} <Grid item xs={4}> <Controller name={`rows.${index}.desc`} control={control} render={({ field }) => ( <TextField {...field} label="描述" multiline minRows={2} maxRows={6} variant="outlined" fullWidth error={!!errors?.rows?.[index]?.desc} helperText={errors?.rows?.[index]?.desc?.message} /> )} /> </Grid> {/* 删除当前行按钮 */} <Grid item xs={1.5}> <Button variant="outlined" color="error" onClick={() => remove(index)} disabled={fields.length === 1} sx={{ mt: 0.5 }} > 删除 </Button> </Grid> </Grid> ))} {/* 底部操作按钮区 */} <Box mt={3} display="flex" gap={2}> <Button variant="contained" onClick={() => append({ text1: '', text2: '', selectVal: '', desc: '' })} > 新增行 </Button> <Button variant="outlined" color="error" onClick={() => pop()} disabled={fields.length === 1} > 删除最后一行 </Button> <Button variant="contained" color="success" type="submit"> 提交表单 </Button> </Box> </form> </Box> ); } export default DynamicForm;
核心特性说明
- 动态行管理:使用react-hook-form提供的
useFieldArray处理行增删,无需手动维护useState和数组操作,自动保留已有行的输入数据,新增行默认值为空。 - 受控组件适配:所有Material UI组件都通过
Controller包裹,自动绑定表单状态,无需手动编写onChange、value同步逻辑。 - Yup校验:通过嵌套数组校验规则,支持逐行逐字段校验,错误信息自动对应到对应行的对应字段,提交时自动触发全量校验。
- 组件适配:TextArea设置
multiline+minRows/maxRows实现高度自适应,所有样式基于Material UI默认规范,可直接修改扩展。
内容的提问来源于stack exchange,提问作者Micro Kumar
相关产品推荐
相关产品推荐

