У меня есть пользовательский компонент, в котором родительский и массив дочерних компонентов тесно связаны, и между родительским и дочерним компонентами существует «черный ход» с использованием функции предоставления-инжекта, предоставляемой vue.
В родительском компоненте я хотел бы отслеживать дочерние компоненты в том порядке, в котором они определены в слоте.
Я знаю, что при подключении дочерние компоненты создаются в том порядке, в котором они определены. Однако во время выполнения дочерний компонент может быть вставлен, например, между первым дочерним компонентом и вторым дочерним компонентом. Вопрос в том, как узнать обновленный порядок дочерних компонентов в родительском слоте?
Я видел в vue devtools, что он может распознавать порядок дочерних и вложенных компонентов на вкладке «Компоненты» и отображать его в виде дерева. Как я могу добиться чего-то подобного?
Вот пример кода https://stackblitz.com/edit/vitejs-vite-pkqhkv?file=src%2FApp.vue
В приведенном выше примере, если вы нажмете «Предыдущий» и «Далее», он должен отобразить слайд 1 и слайд 3, как и ожидалось, поскольку слайд 2 отключен, но когда вы включаете слайд 2, порядок меняется на 1, 3, 2, что не то, что я хочу. Порядок должен быть 1, 2 и 3.
Очевидно, что реализация предоставления каждому дочернему элементу нумерованного индекса не является надежной, поскольку они могут быть зарегистрированы в разное время, поэтому я хочу реализовать по-другому, чтобы я мог знать текущий порядок дочерних компонентов в любой момент времени.
Слоты передаются как массив объектов, содержимое внутреннего элемента передается как значение внутри объекта. добавлены новые слоты с использованием сращивания массивов. Вы можете использовать v-html или другие методы, если дочерний компонент имеет больше элементов.
Родительский компонент
<template>
<div>
<slot></slot>
<ul>
<li v-for = "(s,i) in slotsArray" :key = "i">
<slot :name = "s.name"></slot>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
slotsArray: { type: Array, default: null }
}
};</script>
Использование родительских компонентов и добавление слотов
<template>
<div>
<!-- Parent Component -->
<parent-component :slotsArray = "slotsArray">
<button @click = "addSlotToIndex(2, 'Item 21')">Add Item on 2nd
Index</button>
<button @click = "addSlotToIndex(0, 'Item 00')">Add Item on 1st
Index</button>
<template v-for = "(s) in slotsArray">
<template :slot = "s.name">
<p :key = "s.i">{{s.child}}</p>
</template>
</template>
</parent-component></div></template>
<script>
import ParentComponent from './views/parent-child-
slots/ParentComponent.vue';
export default {
name: 'App',
components: { ParentComponent },
data () {
return {
slotsArray: [
{
name: 'slota',
child: 'Item 1'
},
{
name: 'slotb',
child: 'Item 2'
},
{
name: 'slotc',
child: 'Item 3'
}
],
addSlotToIndex (idx, value) {
this.slotsArray.splice(idx, 0, {
name: idx > 0 ? this.slotsArray[idx - 1].name + 'a' :
this.slotsArray[0].name + 'a',
child: value
});
}
};
}};
спасибо за ответ, но я бы предпочел не передавать массив в качестве реквизита, потому что я хочу, чтобы потребитель мог передавать шаблон в дочернем компоненте. вы можете посмотреть первый пост, где я поделился ссылкой на пример кода.
Даже ваши родительские/дочерние компоненты сильно связаны, вам лучше не передавать состояние видимости слайда дочерним элементам. Это не очень хороший дизайн, слишком большая утечка состояния слайда. Сделайте всю логику слайдов внутри родителя (хороший дизайн и инкапсуляция).
Родитель.vue
<script setup>
import { ref, useSlots, watch, computed } from 'vue';
// the current component index
const current = ref(0);
const slots = useSlots();
// collect all slot child components
const $children = computed(() => slots.default().filter(vnode => vnode.type.render)); // remove all non-component vnodes
// watch for deletion of the current component and move to the next one
watch(() => $children.value.length, length => current.value < length || next(current.value - 1));
function prev(idx = current.value) {
if (--idx < 0) idx = $children.value.length - 1;
current.value = idx;
}
function next(idx = current.value) {
if (++idx >= $children.value.length) idx = 0;
current.value = idx;
}
defineExpose({
prev,
next,
});
</script>
<template>
<section>
<div>$children.length: {{ $children.length }}, current: {{ current }}</div>
<component :is = "() => $children.filter((_, idx)=> idx === current)" />
</section>
</template>
App.vue
<script setup>
import Parent from './Parent.vue';
import Child from './Child.vue';
import { ref } from 'vue';
const toggleSlide2 = ref(false);
const toggleSlide3 = ref(true);
const parent = ref();
</script>
<template>
<div>
<section>
<button @click = "parent.prev()">Prev</button>
<button @click = "parent.next()">Next</button>
<button @click = "toggleSlide2 = !toggleSlide2">Toggle Slide 2</button>
<button @click = "toggleSlide3 = !toggleSlide3">Toggle Slide 3</button>
</section>
<Parent ref = "parent">
<Child>
<h1>Slide 1</h1>
</Child>
<Child v-if = "toggleSlide2">
<h1>Slide 2</h1>
</Child>
<Child v-if = "toggleSlide3">
<h1>Slide 3</h1>
</Child>
</Parent>
</div>
</template>
спасибо за ответ, но родитель и ребенок созданы для сильной связи
@icube да, имейте сильную связь, предоставляйте/внедряйте, без проблем. но, по моему мнению, утечка скользящего механизма в детей - это плохой дизайн. нет необходимости в этом
@icube я обновил ответ, чтобы уточнить это
спасибо за разъяснения, но причина моего вопроса в том, чтобы спросить, есть ли способ отслеживать порядок дочерних элементов от родителя, как указано в слоте, без необходимости явно устанавливать порядок дочерним элементом. если бы у меня был ребенок, который мог бы явно установить порядок, я бы вообще не задавал вопрос, поскольку знаю, что могу легко получить информацию
@icube, что не так с моим решением? у вас есть логика слайдов, которая работает так, как вас просят
в вашем решении нет ничего плохого, и оно работает, да, но мой вопрос заключается в том, как отслеживать порядок дочерних элементов в родительском слоте в том порядке, в котором они были объявлены в слоте, без явного указания ключа заказа.
Продолжение Первого ответа. Попробуйте передать дочерний компонент таким образом. Недостатком является необходимость добавить html как строку и привязать как v-html.
<parent-component :slotsArray = "slotsArray">
<button @click = "addSlotToIndex(2, 'Item 21')">Add Item on 2nd
Index</button>
<button @click = "addSlotToIndex(0, 'Item 00')">Add Item on 1st
Index</button>
<template v-for = "(s) in slotsArray">
<template :slot = "s.name">
<child-component :inner-html = "s.child" :key = "s.i"></child-component>
</template>
</template>
</parent-component>
Дочерний компонент
<template>
<component :is = "tag">
<slot>
<div v-html = "innerHtml"></div>
</slot>
</component></template>
<script>
export default {
props: {
innerHtml: { type: String, default: '' },
tag: { type: String, default: 'div' }
}
};</script>
Примечание. Может оказаться бесполезным, если дочерний компонент сложен и содержит больше элементов.
Пожалуйста, укажите stackoverflow.com/help/mcve для решения вашей проблемы. Это зависит от способа динамического изменения порядка. Если есть v-for, вы можете отслеживать массив и ключи, которые определяют, что он отображает.