Как создать пользовательский компонент выбора с параметрами, передаваемыми в виде HTML вместо реквизита в Vue 3?

Я пытаюсь создать собственный компонент выбора, который может принимать параметры в формате 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 и MyOption - так что, предположительно, вы либо сделали что-то не так, либо ничего не сделали

Jaromanda X 16.03.2024 09:51

какой код я должен показать? Я спрашиваю, как это реализовать, потому что сам не смог. У меня есть версия MySelect, но она принимает опции в качестве опоры. Ты хочешь это?

John Baker 16.03.2024 09:53

Что ж, поскольку Stack Overflow не является сервисом для написания кода, вероятно, лучше, если вы покажете, что вы хотя бы попытались решить свою «проблему» — именно так это место и должно работать.

Jaromanda X 16.03.2024 09:58

Я добавил свою версию компонента. Взгляни.

John Baker 16.03.2024 10:08

Если вы хотите использовать свои <MyOption компоненты, вам понадобится слот

Jaromanda X 16.03.2024 10:11

Я понятия не имею, как реализовать это с помощью слотов. Если у вас есть один внутренний тег, да, вы можете использовать слоты. Но здесь у вас есть произвольное количество вариантов. Как бы Вы это сделали? И это не просто раскрывающийся список, в котором вы отображаете содержимое. Вам нужно обработать событие щелчка для каждой опции и получить их значение.

John Baker 16.03.2024 10:16

Даже если вы, скажем, обрабатываете событие щелчка внутри компонента MyOption, как бы вы передали его значение обратно в MySelect?

John Baker 16.03.2024 10:18

Вы уверены, что вам нужно использовать этот синтаксис? Почему бы не использовать массив, переданный как :data=...~ для MyOption

Tachibana Shin 16.03.2024 10:26

Ваша идея в принципе осуществима, но зачем это делать?

Tachibana Shin 16.03.2024 10:27

Как это связано с заданным вопросом? Это ничего не решает.

John Baker 16.03.2024 10:29

Если параметры переданы как реквизит, вы не можете стилизовать их отдельно. Это основной момент передачи параметров в виде HTML.

John Baker 16.03.2024 10:31

Вам обязательно нужно конвертировать их в html вот так, как же их визуализировать? В конце концов, вам все равно придется использовать v-for, как при передаче массива.

Tachibana Shin 16.03.2024 10:39

Это необходимо реализовать с помощью функции рендеринга, это более гибко. MySelect получает vnodes MyOption через слот и может читать myOptionVnode.props и myOptionVnode.children (это не слот, пока он не отрисован), например stackoverflow.com/a/73000029/3731501 . Комментарий выше относительно :data имеет смысл: он будет делать то же самое, если структура данных правильная. Вы ничего не получите от «html», за исключением того, что он будет выглядеть аккуратнее (это не настоящий HTML, это vnodes, на этом этапе нет никакого стиля)

Estus Flask 16.03.2024 10:40

Возможно, вы не понимаете vue. Если вы думаете, что передача массива не может быть передана пользовательским компонентом, то это неправильно.

Tachibana Shin 16.03.2024 10:40

Я реализую вашу идею, хотя я действительно не знаю, что вы пытаетесь сделать.

Tachibana Shin 16.03.2024 10:44

@EstusFlask Я рассмотрю функцию рендеринга. Но почему вы говорите, что передача параметров в формате html не решает проблему? Это даст вам контроль над стилизацией каждого параметра отдельно.

John Baker 16.03.2024 11:13

@JohnBaker Это просто еще один способ передать данные компоненту: вы можете обрабатывать объект данных так, как вам нужно. Это может быть что-то вроде :data = "[{ content: ..., props: ...}] не относиться к стилю, классу и т. д. реквизиту особым образом.

Estus Flask 16.03.2024 12:14
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
17
873
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Настройка элементов списка обычно выполняется с помощью слотов с ограниченной областью действия:

ИГРОВАЯ ПЛОЩАДКА VUE SFC

<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>

Вы также можете автоматически обрабатывать щелчок выбора, но для этого вам понадобится специальный компонент слота для параметров рендеринга. Кроме того, вы можете использовать свой собственный компонент для перехода в слот:

ИГРОВАЯ ПЛОЩАДКА VUE SFC

<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, чтобы получить эти данные обратно в параметре для стилизации. Это в некоторой степени дает решение. Но я не думаю, что это самый чистый способ сделать это.

John Baker 16.03.2024 10:57

@JohnBaker это рекомендуемый документацией Vue способ настройки элементов списка. кстати, я еще не закончил свой ответ, ваше решение будет последним 😉

Alexander Nenashev 16.03.2024 11:07

@JohnBaker добавил «вероятно, более» чистое решение

Alexander Nenashev 16.03.2024 11:15

да, выглядит чище. Но знаете ли вы, как это реализовать аналогично этому: bootstrap-vue.org/docs/comComponents/form-select. Посмотрите раздел, где написано «укажите свои варианты вручную». Потому что это именно то, чего я хотел достичь. Это будет иметь больше смысла, особенно если у вас есть три или четыре варианта.

John Baker 16.03.2024 11:28

@JohnBaker добавил желаемое решение в качестве отдельного ответа.

Alexander Nenashev 16.03.2024 11:31
Ответ принят как подходящий

Вы можете создать собственную функцию рендеринга слотов и собирать слоты по умолчанию для дочерних компонентов с слотами:

ИГРОВАЯ ПЛОЩАДКА VUE SFC

Обычно я бы предпочел решение с ограниченным слотом, опубликованное в моем другом ответе здесь, НО никто не мешает ОБЪЕДИНИТЬ 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>

Работает как шарм. Я не знал, что переменные можно использовать в качестве компонента, но рассмотрю это. Огромное спасибо!

John Baker 16.03.2024 12:02

@JohnBaker вот документация: vuejs.org/guide/extras/render-function

Alexander Nenashev 16.03.2024 12:15

Другие вопросы по теме