如何在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
相关产品推荐
相关产品推荐

