Radio单选组件开发中的常见问题与最佳实践

夏侯小秋 组件 阅读 2,104
赞 15 收藏
二维码
手机扫码查看
反馈

先看效果,再看代码

做表单的时候,Radio 单选框几乎是绕不开的组件。别看它简单,真用起来各种细节问题一大堆。我之前在一个问卷项目里,光是 Radio 的选中状态和数据同步就折腾了大半天。今天分享几个亲测有效的写法,直接上代码,少废话。

Radio单选组件开发中的常见问题与最佳实践

最基础的用法,HTML 原生写法其实就够用了,但要注意 name 必须一致,不然就不是“单选”了:

<label>
  <input type="radio" name="gender" value="male" /> 男
</label>
<label>
  <input type="radio" name="gender" value="female" /> 女
</label>

但这种写法在现代项目里基本没人手写了,尤其是 React/Vue 项目。我更推荐用受控组件的方式,把状态交给 JS 管理,这样后续处理表单、校验、提交都方便。

React 里怎么写才不翻车

在 React 中,我习惯封装一个 RadioGroup 组件,避免每个页面都重复写一堆 useStateonChange。下面是我现在项目里用的简化版:

import { useState } from 'react';

function RadioGroup({ options, value, onChange }) {
  return (
    <div>
      {options.map(option => (
        <label key={option.value} style={{ display: 'block', margin: '8px 0' }}>
          <input
            type="radio"
            name="radio-group"
            value={option.value}
            checked={value === option.value}
            onChange={(e) => onChange(e.target.value)}
          />
          {option.label}
        </label>
      ))}
    </div>
  );
}

// 使用
function App() {
  const [selected, setSelected] = useState('option1');
  const options = [
    { value: 'option1', label: '选项一' },
    { value: 'option2', label: '选项二' },
    { value: 'option3', label: '选项三' },
  ];

  return (
    <RadioGroup
      options={options}
      value={selected}
      onChange={setSelected}
    />
  );
}

这个写法最大的好处是:逻辑清晰,复用性强。而且 checked 是明确控制的,不会出现“点了没反应”的玄学问题。

踩坑提醒:这三点一定注意

我在多个项目里反复踩过这些坑,列出来帮你省点时间:

  • 不要用 defaultChecked 混合受控和非受控:React 里一旦用了 valuechecked,就必须全程受控。如果你用了 defaultChecked,又在后面动态改状态,控制台会警告你,而且行为可能不符合预期。
  • label 和 input 的关联要完整:虽然上面代码里我把 input 包在 label 里,这是最简单的做法。但如果你因为 UI 需要把它们分开(比如用图标代替默认圆点),一定要用 id + for 关联,否则点击文字无法选中。亲测移动端 Safari 对这个特别敏感。
  • <样式重置别偷懒:不同浏览器对 radio 的默认样式差异很大,尤其在移动端。我建议直接用 CSS 重置,或者用伪元素自定义。别指望靠原生样式跨端一致。

举个自定义样式的例子,用 Tailwind 写的(实际项目里可能还要加 focus、disabled 状态):

<label class="flex items-center cursor-pointer">
  <input type="radio" name="choice" class="sr-only" />
  <span class="w-4 h-4 border-2 rounded-full border-gray-400 mr-2"></span>
  <span>自定义选项</span>
</label>

这里用 sr-only 隐藏原生 input,用 span 模拟外观。关键是要保留 input 的可访问性,不能直接删掉 input。

这个场景最好用:动态选项 + 异步加载

有时候选项不是写死的,而是从接口拉的。比如用户选择“国家”,然后根据国家动态加载“省份”。这时候要注意:**初始值可能不在选项列表里**。

我之前遇到一个 bug:用户编辑表单时,后端返回的已选值是 “province_999”,但新接口返回的省份列表里没有这个值(可能是历史数据),结果 Radio 就全没选中,用户以为没选,其实只是选项变了。

解决方案很简单:在设置 state 之前,先判断选项是否存在。如果不存在,可以设为 null 或者保留原值(看业务需求):

useEffect(() => {
  fetch('/api/provinces')
    .then(res => res.json())
    .then(data => {
      setOptions(data);
      // 如果当前选中的值在新选项里,就保留;否则清空
      if (data.some(opt => opt.value === currentSelected)) {
        setSelected(currentSelected);
      } else {
        setSelected(null); // 或者设为默认值
      }
    });
}, [country]);

这个细节很容易被忽略,但上线后会被用户疯狂吐槽“我的选择怎么没了”。

高级技巧:用 Radio 实现 Tab 切换

别笑,这招我真用过。有些轻量级的 Tab 切换,用 Radio 比写一堆状态管理还清爽,尤其适合静态内容切换。

<div class="radio-tabs">
  <input type="radio" name="tab" id="tab1" checked />
  <input type="radio" name="tab" id="tab2" />
  
  <label for="tab1">Tab 1</label>
  <label for="tab2">Tab 2</label>
  
  <div class="tab-content">
    <div class="tab-pane">内容1</div>
    <div class="tab-pane">内容2</div>
  </div>
</div>
.radio-tabs input[type="radio"] {
  display: none;
}
.tab-content .tab-pane {
  display: none;
}
#tab1:checked ~ .tab-content .tab-pane:nth-child(1),
#tab2:checked ~ .tab-content .tab-pane:nth-child(2) {
  display: block;
}

纯 CSS 实现,零 JS,适合 SEO 友好的静态页。当然,复杂交互还是得上 JS,但这种小场景用起来贼爽。

最后说两句

Radio 看似简单,但细节决定体验。我现在的项目里,所有表单组件都做了统一封装,包括 Radio、Checkbox、Select,确保状态管理、校验、无障碍都一致。如果你还在每个页面手写,建议早点抽离。

以上是我踩坑后的总结,希望对你有帮助。这个技巧的拓展用法还有很多(比如和 Formik、Zod 结合做表单校验),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论