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

React父组件点击添加按钮前,如何获取子组件数据并校验输入?

解决方案

要实现点击「Add Question」时先校验所有已有表单再添加新组件,有两种优雅的实现思路,可根据组件设计需求选择:

方案一:通过 Refs 调用子组件的校验方法

这种方式适合让子组件独立维护自身表单状态,父组件仅负责触发校验和添加逻辑。

1. 改造父组件 CreateGame

使用 useRef 存储所有 QuestionForm 的实例引用,点击按钮时遍历调用子组件的校验方法:

import { useState, useRef } from 'react';
import { nanoid } from 'nanoid';

function CreateGame() {
  const [questionsArray, setQuestionsArray] = useState([
    { value: true, id: nanoid() },
  ]);
  // 创建ref数组存储子组件实例
  const questionFormRefs = useRef([]);

  const addQuestion = () => {
    // 遍历所有子组件,执行校验
    let allValid = true;
    questionFormRefs.current.forEach(ref => {
      if (ref && !ref.validate()) {
        allValid = false;
      }
    });

    // 所有表单校验通过才添加新组件
    if (allValid) {
      setQuestionsArray(prevState => [
        ...prevState,
        { value: true, id: nanoid() },
      ]);
    }
  };

  return (
    <div id="create-game">
      <h1>Quiz Creator</h1>
      <div className="container">
        {questionsArray.map((question, index) => (
          <QuestionForm
            key={question.id}
            id={question.id}
            // 将ref存入数组,注意index对应
            ref={el => questionFormRefs.current[index] = el}
          />
        ))}
      </div>
      <div className="buttons">
        <button onClick={addQuestion}>
          Add Question
        </button>
        <button>Create Quiz</button>
      </div>
    </div>
  );
}

2. 改造子组件 QuestionForm

使用 forwardRef 暴露校验方法,实现自身字段的校验逻辑:

import { useState, forwardRef, useImperativeHandle } from 'react';

const QuestionForm = forwardRef((props, ref) => {
  const [formData, setFormData] = useState({
    question: '',
    answerOne: '',
    answerTwo: '',
    answerThree: '',
    answerFour: '',
    correctAnswer: ''
  });
  const [errors, setErrors] = useState({});

  // 定义校验方法,暴露给父组件
  const validate = () => {
    const newErrors = {};
    // 检查所有字段是否为空
    Object.keys(formData).forEach(key => {
      if (!formData[key].trim()) {
        newErrors[key] = '此字段不能为空';
      }
    });
    setErrors(newErrors);
    // 返回校验结果:无错误则通过
    return Object.keys(newErrors).length === 0;
  };

  // 暴露校验方法给父组件的ref
  useImperativeHandle(ref, () => ({
    validate
  }));

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
    // 输入时清除对应字段的错误提示
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }));
    }
  };

  return (
    <div className="question-form">
      <input
        name="question"
        value={formData.question}
        onChange={handleChange}
        placeholder="输入问题"
      />
      {errors.question && <span className="error">{errors.question}</span>}
      
      <input
        name="answerOne"
        value={formData.answerOne}
        onChange={handleChange}
        placeholder="选项1"
      />
      {errors.answerOne && <span className="error">{errors.answerOne}</span>}

      <input
        name="answerTwo"
        value={formData.answerTwo}
        onChange={handleChange}
        placeholder="选项2"
      />
      {errors.answerTwo && <span className="error">{errors.answerTwo}</span>}

      <input
        name="answerThree"
        value={formData.answerThree}
        onChange={handleChange}
        placeholder="选项3"
      />
      {errors.answerThree && <span className="error">{errors.answerThree}</span>}

      <input
        name="answerFour"
        value={formData.answerFour}
        onChange={handleChange}
        placeholder="选项4"
      />
      {errors.answerFour && <span className="error">{errors.answerFour}</span>}

      <input
        name="correctAnswer"
        value={formData.correctAnswer}
        onChange={handleChange}
        placeholder="正确答案"
      />
      {errors.correctAnswer && <span className="error">{errors.correctAnswer}</span>}
    </div>
  );
});

export default QuestionForm;

方案二:状态提升到父组件统一管理

这种方式将所有表单数据交给父组件维护,校验逻辑直接在父组件完成,更符合React的状态管理理念,也方便后续提交整个测验数据。

1. 改造父组件 CreateGame

维护所有问题的表单数据,点击按钮时直接校验所有数据:

import { useState } from 'react';
import { nanoid } from 'nanoid';

function CreateGame() {
  // 初始化包含一个空的表单数据对象
  const [questionsData, setQuestionsData] = useState([
    {
      id: nanoid(),
      question: '',
      answerOne: '',
      answerTwo: '',
      answerThree: '',
      answerFour: '',
      correctAnswer: ''
    }
  ]);

  const addQuestion = () => {
    // 校验所有已有表单数据
    let allValid = true;
    questionsData.forEach(question => {
      // 跳过id字段,校验其余输入字段
      Object.entries(question).forEach(([key, value]) => {
        if (key !== 'id' && !value.trim()) {
          allValid = false;
        }
      });
    });

    if (allValid) {
      // 添加新的空表单数据
      setQuestionsData(prev => [
        ...prev,
        {
          id: nanoid(),
          question: '',
          answerOne: '',
          answerTwo: '',
          answerThree: '',
          answerFour: '',
          correctAnswer: ''
        }
      ]);
    }
  };

  const handleQuestionChange = (id, field, value) => {
    setQuestionsData(prev => prev.map(q => 
      q.id === id ? { ...q, [field]: value } : q
    ));
  };

  return (
    <div id="create-game">
      <h1>Quiz Creator</h1>
      <div className="container">
        {questionsData.map((question) => (
          <QuestionForm
            key={question.id}
            data={question}
            onChange={(field, value) => handleQuestionChange(question.id, field, value)}
          />
        ))}
      </div>
      <div className="buttons">
        <button onClick={addQuestion}>
          Add Question
        </button>
        <button>Create Quiz</button>
      </div>
    </div>
  );
}

2. 改造子组件 QuestionForm

变成受控组件,仅负责渲染和传递输入变更:

const QuestionForm = ({ data, onChange }) => {
  const handleChange = (e) => {
    const { name, value } = e.target;
    onChange(name, value);
  };

  return (
    <div className="question-form">
      <input
        name="question"
        value={data.question}
        onChange={handleChange}
        placeholder="输入问题"
      />
      <input
        name="answerOne"
        value={data.answerOne}
        onChange={handleChange}
        placeholder="选项1"
      />
      <input
        name="answerTwo"
        value={data.answerTwo}
        onChange={handleChange}
        placeholder="选项2"
      />
      <input
        name="answerThree"
        value={data.answerThree}
        onChange={handleChange}
        placeholder="选项3"
      />
      <input
        name="answerFour"
        value={data.answerFour}
        onChange={handleChange}
        placeholder="选项4"
      />
      <input
        name="correctAnswer"
        value={data.correctAnswer}
        onChange={handleChange}
        placeholder="正确答案"
      />
    </div>
  );
};

export default QuestionForm;

方案选择建议

  • 如果QuestionForm需要复杂的独立校验逻辑(比如字段格式验证、实时错误提示),选方案一,子组件可以自主处理校验细节。
  • 如果后续需要统一提交所有测验数据,或者希望集中管理状态,选方案二,父组件直接持有所有数据,后续扩展更方便。

内容的提问来源于stack exchange,提问作者Bruno Oliveira

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 21:32:03