ElementPlus 单选框取消选择方案

前言

Element Plus 的单选框默认无法取消已选中的选择,有些业务场景下,不做选择是有意义的,如何才能取消已选中的选择?

方案

方案一:拦截 Radio 组件点击事件,手动更新 Radio Group 的取值

点击已选择的 Radio 取消选择

在线演示:

vue
<template>
  <el-radio-group v-model="radio">
    <el-radio
      v-for="o in list"
      :key="o.value"
      v-bind="o"
      @click.prevent="handleRadioClick(o.value)"
    />
  </el-radio-group>
</template>

<script setup>
import { ref } from 'vue'

const radio = ref('')
const list = [
  { value: 'beijing', label: '北京' },
  { value: 'shanghai', label: '上海' },
  { value: 'shenzhen', label: '深圳' },
]

function handleRadioClick(val) {
  radio.value = radio.value === val ? null : val
}
</script>

方案二:新增一个空白 Radio

实际上没有“取消选择”,而是新增一个空白 Radio 标识“不选择”

vue
<template>
  <el-radio-group v-model="radio">
    <el-radio v-for="o in list" :key="o.value" v-bind="o" />
    <el-radio value="" label="空" />
  </el-radio-group>
</template>

<script setup>
import { ref } from 'vue'

const radio = ref('')
const list = [
  { value: 'beijing', label: '北京' },
  { value: 'shanghai', label: '上海' },
  { value: 'shenzhen', label: '深圳' },
]
</script>

对比

虽然方案二在视觉上增加了一个看似“冗余”的选项,但在交互设计和代码维护上,它才是更成熟的解决方案。

方案一(点击反选)的局限性:

  • 违背用户心智模型:单选框的语义是“互斥且必选其一”,默认 Radio 点击后无法取消,用户需要“猜”或“试”才知道这个 Radio 可以反选
  • 状态表达含糊:如果业务允许“不选”,那么“空值”本身就是一个合法的业务状态。将这个状态隐藏在“未选中”的视觉表现中,不如将其显式化更为直观

方案二(显式选项)的优势:

  • 交互的确定性:新增“不适用/无/空”选项,明确地告诉用户:“不选择”也是一种有效的选择,这符合系统状态可见性原则。
  • 数据流清晰:从数据层面看,Radio Group 应当始终映射一个确定的值。比通过 Hack 点击事件来置空数据,逻辑更加顺畅且易于维护。

综上,建议采取第二种方案。

如果业务场景确实需要“可取消”且不希望“空”选项,可能这里就不该用单选框,更规范的做法是改用 Select 选择器(带清除功能)。

替换方案

带清除功能的选择器

vue
<template>
  <el-select v-model="select" clearable>
    <el-option v-for="o in list" :key="o.value" v-bind="o" />
  </el-select>
</template>

<script setup>
import { ref } from 'vue'

const select = ref('')
const list = [
  { value: 'beijing', label: '北京' },
  { value: 'shanghai', label: '上海' },
  { value: 'shenzhen', label: '深圳' },
]
</script>

重置表单

如果单选框是表单的一部分,且存在“重置表单”的按钮,也可以重置表单作为取消单选的辅助方案。虽然交互上比较糟糕,但也不违反用户直觉

vue
<template>
  <el-form ref="formRef" :model="form" label-width="auto">
    <el-form-item label="城市" prop="radio">
      <el-radio-group v-model="form.radio">
        <el-radio v-for="o in list" :key="o.value" v-bind="o" />
      </el-radio-group>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="() => formRef?.resetFields()">Reset</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup>
import { reactive, ref } from 'vue'

const form = reactive({ radio: '' })
const formRef = ref()
const list = [
  { value: 'beijing', label: '北京' },
  { value: 'shanghai', label: '上海' },
  { value: 'shenzhen', label: '深圳' },
]
</script>