React单选按钮与动态输入字段代码重构方案咨询
React 动态单选输入组重构方案
你的代码重复问题本质是3组单选+动态输入的逻辑结构完全一致,通过配置化抽象+通用逻辑封装即可在不改动原有受控规则、交互行为的前提下,大幅精简代码,完全遵循DRY原则。
核心重构思路
把不同组之间的差异项(字段名、显示文案、按钮文案)抽成静态配置,合并分散的独立状态,封装通用事件处理函数,通过遍历配置渲染UI,消除所有重复代码。
具体实现步骤
1. 抽离静态配置
把所有和业务组相关的差异值统一维护,后续新增/修改分组只需要改配置,不需要动核心逻辑:
import { Fragment, useState } from 'react'; // 分组配置:所有差异化内容统一在此维护 const GROUP_CONFIG = [ { fieldKey: 'via', groupLabel: 'Do you have a VIA?', addBtnText: 'Add VIA', removeBtnText: 'Remove VIA', placeholder: 'Enter The Via (Title, name of activity (Code), location when needed' }, { fieldKey: 'references', groupLabel: 'Do you have References', addBtnText: 'Add Ref', removeBtnText: 'Remove Ref', placeholder: 'Enter The Reference (Title, name of activity (Code), location when needed' }, { fieldKey: 'enclosures', groupLabel: 'Do you have Enclosures', addBtnText: 'Add Encl', removeBtnText: 'Remove Encl', placeholder: 'Enter The Enclosure (Title, name of activity (Code), location when needed' } ]; // 单组最多允许添加的输入框数量 const MAX_INPUT_PER_GROUP = 3;
2. 合并分散的独立状态
原代码6个独立useState可以合并为2个统一的状态对象,减少冗余声明:
const Radios = () => { // 统一管理所有单选的选中状态,key对应配置的fieldKey const [radioChecked, setRadioChecked] = useState( GROUP_CONFIG.reduce((acc, item) => ({ ...acc, [item.fieldKey]: false }), {}) ); // 统一管理所有动态输入框的值,key对应配置的fieldKey const [inputValues, setInputValues] = useState( GROUP_CONFIG.reduce((acc, item) => ({ ...acc, [item.fieldKey]: [] }), {}) );
3. 封装通用事件处理函数
原代码9个重复的事件处理函数,只需要4个通用函数即可覆盖所有场景,逻辑和原代码完全一致:
// 单选切换逻辑:选是初始化第一个输入框,选否清空对应输入组 const handleRadioToggle = (fieldKey) => { const newCheckedState = !radioChecked[fieldKey]; setRadioChecked(prev => ({ ...prev, [fieldKey]: newCheckedState })); setInputValues(prev => ({ ...prev, [fieldKey]: newCheckedState ? [{ [fieldKey]: '' }] : [] })); }; // 新增输入框 const handleAddInput = (fieldKey) => { setInputValues(prev => ({ ...prev, [fieldKey]: [...prev[fieldKey], { [fieldKey]: '' }] })); }; // 删除指定输入框 const handleRemoveInput = (fieldKey, removeIndex) => { setInputValues(prev => ({ ...prev, [fieldKey]: prev[fieldKey].filter((_, idx) => idx !== removeIndex) })); }; // 输入框值更新(完全保留受控组件逻辑) const handleInputChange = (fieldKey, index, e) => { const { value } = e.target; setInputValues(prev => { const updatedList = [...prev[fieldKey]]; updatedList[index][fieldKey] = value; return { ...prev, [fieldKey]: updatedList }; }); };
4. 遍历配置渲染UI
原代码重复写了3遍的单选块、输入块,通过遍历配置统一渲染,样式和交互完全和原代码对齐:
return ( <Fragment> <label className='sm:text-xl mt-7'> Optional Items</label> {GROUP_CONFIG.map((config, groupIdx) => { const { fieldKey, groupLabel, addBtnText, removeBtnText, placeholder } = config; const isChecked = radioChecked[fieldKey]; const currentInputList = inputValues[fieldKey]; const radioGroupName = `radio-${groupIdx + 1}`; return ( <Fragment key={fieldKey}> {/* 单选按钮组 */} <div className='form-control block items-center text-xs my-1 sm:text-lg'> <label className='mb-2 mr-2'>{groupLabel}</label> <input type='radio' name={radioGroupName} value={groupIdx + 1} checked={isChecked === true} onChange={() => handleRadioToggle(fieldKey)} className='radio radio-primary mx-2 radio-xs checked:bg-blue-500 sm:radio-lg sm:m-0' />{' '} Yes <input type='radio' name={radioGroupName} value={groupIdx + 1} checked={isChecked === false} onChange={() => handleRadioToggle(fieldKey)} className='radio mx-2 radio-xs radio-primary checked:bg-red-500 sm:radio-lg' /> No </div> {/* 动态输入框列表 */} {isChecked && currentInputList.map((inputItem, inputIdx) => ( <div key={inputIdx}> <input name={fieldKey} id={fieldKey} value={inputItem[fieldKey]} onChange={(e) => handleInputChange(fieldKey, inputIdx, e)} type='text' className='block w-full text-black text-[8px] my-3 rounded-md py-2 pl-1 pr-2 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-base sm:pl-9 sm:pr-3' placeholder={placeholder} /> {/* 新增按钮:仅最后一个输入框、且未达数量上限时显示 */} {currentInputList.length - 1 === inputIdx && currentInputList.length < MAX_INPUT_PER_GROUP && ( <button type='button' className='btn mr-4 btn-sm sm:btn' onClick={() => handleAddInput(fieldKey)}> {addBtnText} </button> )} {/* 删除按钮:输入框数量大于1时显示 */} {currentInputList.length > 1 && ( <button type='button' className='btn btn-sm sm:btn' onClick={() => handleRemoveInput(fieldKey, inputIdx)}> {removeBtnText} </button> )} </div> ))} </Fragment> ) })} </Fragment> ); }; export default Radios;
重构收益
- 无破坏性改动:100%保留原有受控逻辑、交互规则、UI样式,选「是」展示输入、选「否」清空字段、最多添加3个输入框的行为和原代码完全一致
- 代码精简:消除90%以上重复代码,整体代码量从260+行压缩到130行左右
- 易维护:后续新增/修改同类型分组,只需要修改
GROUP_CONFIG配置即可,不需要新增状态、函数或模板代码 - 修复原代码隐含问题:顺手修复了原代码同组单选id重复、References和Enclosures输入框占位符错误复用VIA文案的小问题
内容的提问来源于stack exchange,提问作者Greg
相关产品推荐
相关产品推荐

