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

基于react-hook-form实现带分支逻辑的动态表单问题求助

React动态表单多选分支跳转优化方案

核心问题定位

当前多选逻辑混乱的根源在于:

  • 未建立选中选项与后续触发问题的关联关系,取消选中时无法精准回溯删除对应分支的问题
  • 问题唯一标识设计(questionId-index)无法区分不同来源触发的同一问题,导致重复判断失效
  • 多选分支的流转逻辑未做隔离,不同选项触发的问题互相干扰

优化实现方案

1. 状态重构:建立来源关联

重新定义核心状态,跟踪每个问题的触发来源,实现精准的分支管理:

import { useState } from 'react';

// 假设questions是API返回的问题数组
const questions = [/* 你的问题数据 */];

function DynamicForm() {
  // 当前展示的问题列表,每个元素携带唯一key和触发来源
  const [formQuestions, setFormQuestions] = useState([
    { ...questions.find(q => q.id === 'name'), key: 'name-init', source: 'init' }
  ]);
  // 记录多选问题的选中状态:{ 问题ID: { 选项ID: 是否选中 } }
  const [selectedChoices, setSelectedChoices] = useState({});
  // 记录每个问题实例的触发来源:{ 问题唯一key: 来源标识 }
  const [questionSources, setQuestionSources] = useState({ 'name-init': 'init' });

  // 生成问题唯一标识:问题ID + 来源标识
  const getQuestionKey = (questionId, source) => `${questionId}-${source}`;

2. 多选选项处理:精准增删分支

实现选中/取消选中时的分支管理逻辑,确保仅操作对应来源的问题:

const handleChoiceChange = (questionId, optionId, checked) => {
    // 更新选中状态
    const updatedChoices = {
      ...selectedChoices,
      [questionId]: {
        ...(selectedChoices[questionId] || {}),
        [optionId]: checked
      }
    };
    setSelectedChoices(updatedChoices);

    const option = questions.find(q => q.id === questionId).choices.find(opt => opt.id === optionId);
    const targetQuestionId = option.goTo;
    const sourceKey = `${questionId}-${optionId}`;
    const targetQuestionKey = getQuestionKey(targetQuestionId, sourceKey);

    if (checked) {
      // 选中时添加目标问题(未触发过则添加)
      const targetQuestion = questions.find(q => q.id === targetQuestionId);
      if (targetQuestion && !questionSources[targetQuestionKey]) {
        setFormQuestions(prev => [...prev, { ...targetQuestion, key: targetQuestionKey }]);
        setQuestionSources(prev => ({ ...prev, [targetQuestionKey]: sourceKey }));
      }
    } else {
      // 取消选中时,递归删除该来源触发的所有后续问题
      const removeKeys = [];
      // 找到当前分支的起始问题索引
      const startIdx = formQuestions.findIndex(q => q.key === targetQuestionKey);
      if (startIdx !== -1) {
        // 收集所有属于该分支的问题key
        let currentIdx = startIdx;
        while (currentIdx < formQuestions.length) {
          const q = formQuestions[currentIdx];
          const source = questionSources[q.key];
          if (source === sourceKey || source?.startsWith(`${sourceKey}-`)) {
            removeKeys.push(q.key);
            currentIdx++;
          } else {
            break;
          }
        }
        // 过滤删除目标问题
        setFormQuestions(prev => prev.filter(q => !removeKeys.includes(q.key)));
        setQuestionSources(prev => Object.fromEntries(
          Object.entries(prev).filter(([key]) => !removeKeys.includes(key))
        ));
      }
    }
  };

3. 输入类问题流转:标准化触发逻辑

统一输入类问题的下一步触发逻辑,与多选分支逻辑对齐:

const handleInputSubmit = (questionId) => {
    const currentQuestion = questions.find(q => q.id === questionId);
    const nextQuestionId = currentQuestion.nextId; // 假设问题包含nextId字段
    const sourceKey = `${questionId}-input`;
    const nextQuestionKey = getQuestionKey(nextQuestionId, sourceKey);

    if (nextQuestionId && !questionSources[nextQuestionKey]) {
      const nextQuestion = questions.find(q => q.id === nextQuestionId);
      setFormQuestions(prev => [...prev, { ...nextQuestion, key: nextQuestionKey }]);
      setQuestionSources(prev => ({ ...prev, [nextQuestionKey]: sourceKey }));
    }
  };

4. 渲染逻辑:根据类型渲染对应组件

return (
    <div className="form-container">
      {formQuestions.map(question => (
        <div key={question.key} className="question-item">
          <h3>{question.title}</h3>
          {question.type === 'input' && (
            <div>
              <input type="text" placeholder={`请输入${question.title}`} />
              <button onClick={() => handleInputSubmit(question.id)}>下一步</button>
            </div>
          )}
          {question.type === 'choice' && (
            <div>
              {question.choices.map(option => (
                <label key={option.id}>
                  <input
                    type="checkbox"
                    checked={selectedChoices[question.id]?.[option.id] || false}
                    onChange={(e) => handleChoiceChange(question.id, option.id, e.target.checked)}
                  />
                  {option.label}
                </label>
              ))}
            </div>
          )}
        </div>
      ))}
    </div>
  );
}

export default DynamicForm;

替代实现思路:状态机管理

如果分支逻辑更复杂,推荐用**状态机(如XState)**管理表单流转,将每个问题作为状态节点,选中/输入操作作为状态转换事件,自动处理分支的进入、退出和回退:

import { createMachine, useMachine } from '@xstate/react';

const formMachine = createMachine({
  id: 'dynamic-form',
  initial: 'name',
  context: {
    selectedOccupations: [],
    formData: {}
  },
  states: {
    name: {
      on: { SUBMIT: 'age' }
    },
    age: {
      on: { SUBMIT: 'occupation' }
    },
    occupation: {
      on: {
        SELECT_DEVELOPER: {
          actions: assign({
            selectedOccupations: (ctx) => [...ctx.selectedOccupations, 'developer']
          }),
          target: 'name'
        },
        SELECT_ENGINEER: {
          actions: assign({
            selectedOccupations: (ctx) => [...ctx.selectedOccupations, 'engineer']
          }),
          target: 'comment'
        },
        DESELECT_DEVELOPER: {
          actions: assign({
            selectedOccupations: (ctx) => ctx.selectedOccupations.filter(o => o !== 'developer')
          }),
          target: (ctx) => ctx.selectedOccupations.includes('engineer') ? 'comment' : 'occupation'
        },
        DESELECT_ENGINEER: {
          actions: assign({
            selectedOccupations: (ctx) => ctx.selectedOccupations.filter(o => o !== 'engineer')
          }),
          target: (ctx) => ctx.selectedOccupations.includes('developer') ? 'name' : 'occupation'
        }
      }
    },
    comment: {
      on: { SUBMIT: 'end' }
    },
    end: { type: 'final' }
  }
});

function StateMachineForm() {
  const [state, send] = useMachine(formMachine);

  // 根据当前状态渲染对应问题
  const renderCurrentQuestion = () => {
    switch (state.value) {
      case 'name':
        return (
          <div>
            <h3>姓名</h3>
            <input type="text" />
            <button onClick={() => send('SUBMIT')}>下一步</button>
          </div>
        );
      // 其他状态渲染逻辑...
    }
  };

  return <div>{renderCurrentQuestion()}</div>;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 02:05:21