基于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_
相关产品推荐
相关产品推荐

