Я пытаюсь создать собственный компонент выбора, который может принимать параметры в формате HTML, например:
<MySelect v-model='value'>
<MyOption value='1'>Option 1</MyOption>
<MyOption value='2'>Option 2</MyOption>
</MySelect>
Это моя текущая реализация пользовательского компонента выбора:
<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>
Этот компонент принимает параметры как свойство и просто как массив строк. Но мне нужно больше настроек параметров, таких как добавление значка или применение стилей к тексту. Таким образом, возможность передавать параметры в формате HTML решила бы эту проблему. Буду признателен, если поделитесь своими идеями, как это можно реализовать!
P.S. Компонент MySelect не должен содержать собственные теги выбора и параметров. Вся цель создания пользовательского компонента выбора заключается в настройке дизайна.
какой код я должен показать? Я спрашиваю, как это реализовать, потому что сам не смог. У меня есть версия MySelect, но она принимает опции в качестве опоры. Ты хочешь это?
Что ж, поскольку Stack Overflow не является сервисом для написания кода, вероятно, лучше, если вы покажете, что вы хотя бы попытались решить свою «проблему» — именно так это место и должно работать.
Я добавил свою версию компонента. Взгляни.
Если вы хотите использовать свои <MyOption компоненты, вам понадобится слот
Я понятия не имею, как реализовать это с помощью слотов. Если у вас есть один внутренний тег, да, вы можете использовать слоты. Но здесь у вас есть произвольное количество вариантов. Как бы Вы это сделали? И это не просто раскрывающийся список, в котором вы отображаете содержимое. Вам нужно обработать событие щелчка для каждой опции и получить их значение.
Даже если вы, скажем, обрабатываете событие щелчка внутри компонента MyOption, как бы вы передали его значение обратно в MySelect?
Вы уверены, что вам нужно использовать этот синтаксис? Почему бы не использовать массив, переданный как :data=...~ для MyOption
Ваша идея в принципе осуществима, но зачем это делать?
Как это связано с заданным вопросом? Это ничего не решает.
Если параметры переданы как реквизит, вы не можете стилизовать их отдельно. Это основной момент передачи параметров в виде HTML.
Вам обязательно нужно конвертировать их в html вот так, как же их визуализировать? В конце концов, вам все равно придется использовать v-for, как при передаче массива.
Это необходимо реализовать с помощью функции рендеринга, это более гибко. MySelect получает vnodes MyOption через слот и может читать myOptionVnode.props и myOptionVnode.children (это не слот, пока он не отрисован), например stackoverflow.com/a/73000029/3731501 . Комментарий выше относительно :data имеет смысл: он будет делать то же самое, если структура данных правильная. Вы ничего не получите от «html», за исключением того, что он будет выглядеть аккуратнее (это не настоящий HTML, это vnodes, на этом этапе нет никакого стиля)
Возможно, вы не понимаете vue. Если вы думаете, что передача массива не может быть передана пользовательским компонентом, то это неправильно.
Я реализую вашу идею, хотя я действительно не знаю, что вы пытаетесь сделать.
@EstusFlask Я рассмотрю функцию рендеринга. Но почему вы говорите, что передача параметров в формате html не решает проблему? Это даст вам контроль над стилизацией каждого параметра отдельно.
@JohnBaker Это просто еще один способ передать данные компоненту: вы можете обрабатывать объект данных так, как вам нужно. Это может быть что-то вроде :data = "[{ content: ..., props: ...}] не относиться к стилю, классу и т. д. реквизиту особым образом.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Настройка элементов списка обычно выполняется с помощью слотов с ограниченной областью действия:
<script setup>
import { ref } from 'vue'
import MySelect from './MySelect.vue';
const options = [{id: "1", title: 'Option 1'}, {id: "2", title: 'Option 2'}];
const selected = ref();
</script>
<template>
<my-select :options #option = "{id, title, select}" v-model = "selected">
<div class = "option" @click = "select">id: {{ id }}, title: {{ title }}</div>
</my-select>
</template>
<style scoped>
.option{
padding: 5px 10px;
border: 1px solid gray;
border-radius: 4px;
cursor: pointer;
}
</style>
MySelect.vue:
<template>
<div class = "select">
<div class = "text" @click = "visible=!visible" style = "cursor:pointer">{{modelValue ? options.find(o => o.id === modelValue).title : text}}</div>
<div class = "options" v-if = "visible">
<template v-for = "option in options">
<slot name = "option" v-bind = "{...option, select: () => select(option.id)}">
<span class = "p-4 hover:bg-neutral-1 cursor-pointer capitalize" @click = "select(option.id)">{{option.title}}</span>
</slot>
</template>
</div>
</div>
</template>
<script setup>
import {ref} from 'vue';
const visible = ref(false)
const props = defineProps({
text: {type: String, default: 'Toggle dropdown'},
options: Array,
modelValue: String
})
const emit = defineEmits(["update:modelValue"])
function select(option){
visible.value = false;
emit("update:modelValue", option)
}
</script>
Вы также можете автоматически обрабатывать щелчок выбора, но для этого вам понадобится специальный компонент слота для параметров рендеринга. Кроме того, вы можете использовать свой собственный компонент для перехода в слот:
<script setup>
import { ref } from 'vue'
import MySelect from './MySelect.vue';
import MyOption from './MyOption.vue';
const options = [{id: "1", title: 'Option 1'}, {id: "2", title: 'Option 2'}];
const selected = ref();
</script>
<template>
<my-select :options #option = "{id, title}" v-model = "selected">
<my-option :value = "id">{{ title }}</my-option>
</my-select>
</template>
MySelect.vue
<template>
<div class = "select">
<div class = "text" @click = "visible=!visible" style = "cursor:pointer">{{modelValue ? options.find(o => o.id === modelValue).title : text}}</div>
<div class = "options" v-if = "visible">
<template v-for = "option in options">
<render-option v-if = "$slots.option" v-bind = "option" @click = "select(option.id)"/>
<span v-else class = "p-4 hover:bg-neutral-1 cursor-pointer capitalize" @click = "select(option.id)">{{option.title}}</span>
</template>
</div>
</div>
</template>
<script setup>
import {ref, useSlots, mergeProps} from 'vue';
const visible = ref(false)
const props = defineProps({
text: {type: String, default: 'Toggle dropdown'},
options: Array,
modelValue: String
})
const emit = defineEmits(["update:modelValue"])
const $slots = useSlots();
const renderOption = props => $slots.option(props)
.map(vnode => (Object.assign(vnode.props ??= {}, {onClick: () => select(props.id)}, vnode.props ?? {}), vnode));
function select(option){
visible.value = false;
emit("update:modelValue", option)
}
</script>
MyOption.vue
<script setup>
</script>
<template>
<div class = "option">
<slot></slot>
</div>
</template>
<style scoped>
.option{
padding: 5px 10px;
border: 1px solid gray;
border-radius: 4px;
cursor: pointer;
}
</style>
Спасибо за ответ. Но это не совсем желаемый результат. Здесь вы создаете массив параметров в родительском компоненте, а затем передаете его в MySelect, чтобы получить эти данные обратно в параметре для стилизации. Это в некоторой степени дает решение. Но я не думаю, что это самый чистый способ сделать это.
@JohnBaker это рекомендуемый документацией Vue способ настройки элементов списка. кстати, я еще не закончил свой ответ, ваше решение будет последним 😉
@JohnBaker добавил «вероятно, более» чистое решение
да, выглядит чище. Но знаете ли вы, как это реализовать аналогично этому: bootstrap-vue.org/docs/comComponents/form-select. Посмотрите раздел, где написано «укажите свои варианты вручную». Потому что это именно то, чего я хотел достичь. Это будет иметь больше смысла, особенно если у вас есть три или четыре варианта.
@JohnBaker добавил желаемое решение в качестве отдельного ответа.
Вы можете создать собственную функцию рендеринга слотов и собирать слоты по умолчанию для дочерних компонентов с слотами:
Обычно я бы предпочел решение с ограниченным слотом, опубликованное в моем другом ответе здесь, НО никто не мешает ОБЪЕДИНИТЬ 2 решения в 1: использовать слот по умолчанию отсюда и #option из другого ответа. Таким образом, компонент выбора может питаться обоими слотами.
<script setup>
import { ref } from 'vue'
import MySelect from './MySelect.vue';
import MyOption from './MyOption.vue';
const selected = ref();
</script>
<template>
<my-select v-model = "selected">
<my-option value = "1"><span style = "color:red">Red option</span></my-option>
<my-option value = "2"><span style = "color:blue">Blue option</span></my-option>
</my-select>
</template>
MySelect.vue
<template>
<div class = "select">
<div class = "text" @click = "visible=!visible" style = "cursor:pointer">
<component v-if = "modelValue" :is = "options[modelValue]"/>
<template v-else>{{ text }}</template>
</div>
<div class = "options" v-if = "visible">
<render-options></render-options>
</div>
</div>
</template>
<script setup>
import {ref, useSlots} from 'vue';
const visible = ref(false)
const props = defineProps({
text: {type: String, default: 'Toggle dropdown'},
options: Array,
modelValue: String
})
const options = ref({});
const emit = defineEmits(["update:modelValue"])
const $slots = useSlots();
const renderOptions = () => {
options.value = {};
return $slots.default()
.map(vnode => {
// collection options' default slot
options.value[vnode.props.value] = vnode.children.default;
Object.assign(vnode.props ??= {}, {onClick: () => select(vnode.props.value)}, vnode.props ?? {});
return vnode;
});
}
function select(option){
visible.value = false;
emit("update:modelValue", option)
}
</script>
MyOption.vue
<script setup>
</script>
<template>
<div class = "option">
<slot></slot>
</div>
</template>
<style scoped>
.option{
padding: 5px 10px;
border: 1px solid gray;
border-radius: 4px;
cursor: pointer;
}
</style>
Работает как шарм. Я не знал, что переменные можно использовать в качестве компонента, но рассмотрю это. Огромное спасибо!
@JohnBaker вот документация: vuejs.org/guide/extras/render-function
вы не показали ни кода для компонентов
MySelectиMyOption- так что, предположительно, вы либо сделали что-то не так, либо ничего не сделали