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

如何在Vue 3中创建支持传入HTML选项的自定义Select组件

实现支持HTML自定义选项的Vue Select组件

需求背景

需要创建一个可接收HTML格式选项的自定义MySelect组件,期望使用方式如下:

<MySelect v-model='value'>
  <MyOption value='1'>Option 1</MyOption>
  <MyOption value='2'>Option 2</MyOption>
</MySelect>

当前组件仅支持通过options props传入字符串数组,无法实现选项的定制化(如添加图标、自定义文本样式),且不能使用原生select/option标签,核心目标是实现完全自定义的设计样式。

现有组件实现

<template>
    <div class="select">
        <div class="text" @click="visible=!visible">{{modelValue ?? text}} <IconChevronDown/></div>
        <div class="options" v-if="visible">
            <span v-for="option in options" class="p-4 hover:bg-neutral-1 cursor-pointer capitalize" @click="select(option)">{{option}}</span>
        </div>
    </div>
</template>

<script setup>
const visible = ref(false)
const props = defineProps({
    text: String,
    options: Array,
    modelValue: String
})
const emit = defineEmits(["update:modelValue"])

function select(option){
    visible.value = false;
    emit("update:modelValue", option)
}
</script>

实现思路

1. 构建MyOption子组件

MyOption作为选项的容器,负责传递value属性并保留插槽以支持自定义HTML内容:

<!-- MyOption.vue -->
<template>
  <slot></slot>
</template>

<script setup>
defineProps({
  value: {
    type: [String, Number],
    required: true
  }
})
</script>

2. 改造MySelect组件,利用插槽收集选项数据

通过Vue的useSlotsAPI获取插槽内的MyOption子组件,收集每个选项的value和对应的自定义内容,同时实现选中内容的动态渲染:

<template>
  <div class="select">
    <!-- 选中内容区域:渲染匹配modelValue的自定义选项内容 -->
    <div class="text" @click="visible = !visible">
      <template v-if="selectedOption">
        <component :is="selectedOption.content"></component>
      </template>
      <span v-else>{{ text }}</span>
      <IconChevronDown/>
    </div>
    
    <!-- 下拉选项列表:渲染所有自定义选项内容 -->
    <div class="options" v-if="visible">
      <div 
        v-for="option in collectedOptions" 
        :key="option.value"
        class="p-4 hover:bg-neutral-1 cursor-pointer capitalize"
        @click="select(option)"
      >
        <component :is="option.content"></component>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, useSlots, watch } from 'vue'

const visible = ref(false)
const props = defineProps({
  text: String,
  modelValue: [String, Number]
})
const emit = defineEmits(["update:modelValue"])

const slots = useSlots()

// 计算属性:收集所有合法的MyOption选项
const collectedOptions = computed(() => {
  return slots.default?.().map(item => {
    // 过滤出MyOption组件实例
    if (item.type.name === 'MyOption') {
      return {
        value: item.props.value,
        content: item.children // 保存选项的自定义HTML内容
      }
    }
    return null
  }).filter(Boolean) || []
})

// 计算属性:根据modelValue匹配选中的选项
const selectedOption = computed(() => {
  if (!props.modelValue) return null
  return collectedOptions.value.find(opt => opt.value === props.modelValue)
})

// 选项选中逻辑
function select(option){
  visible.value = false;
  emit("update:modelValue", option.value)
}

// 监听外部modelValue变化,确保选中内容同步
watch(() => props.modelValue, () => {
  // selectedOption会自动响应modelValue变化,无需额外操作
})
</script>

3. 核心细节说明

  • 插槽内容收集:通过useSlots()获取默认插槽的所有子节点,筛选出MyOption组件,提取其value和自定义内容。
  • 动态内容渲染:使用Vue的<component :is="...">组件动态渲染选中项和下拉选项的自定义HTML,保证样式和结构完全可控。
  • 定制化支持:允许在MyOption内部传入任意HTML元素,比如带图标的选项:
    <MySelect v-model='value'>
      <MyOption value='1'>
        <div class="flex items-center gap-2">
          <svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
            <circle cx="12" cy="7" r="4"></circle>
          </svg>
          <span>用户选项 1</span>
        </div>
      </MyOption>
      <MyOption value='2'>
        <span style="color: #3b82f6; font-weight: 600;">高亮选项 2</span>
      </MyOption>
    </MySelect>
    
  • 兼容性:支持modelValue为字符串或数字类型,确保与Vue的v-model双向绑定逻辑兼容。

内容的提问来源于stack exchange,提问作者John Baker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 20:42:14