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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 23:57:17