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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 18:54:02