Для вложенных компонентов Svlete, где Outer содержит несколько Inner, можно ли создать диспетчер с createEventDispatcher в Inner, который будет связываться с экземпляром, вызвавшим диспетчер?
Outer.svelte будет выглядеть примерно так
<script lang = "ts">
import Inner from "./Inner.svelte";
/* placeholder array of 0-10 elements, to make it clear the number of Inners
* in this component is different depedning on some outside factors */
let dynamicContent = Array.from({length: Math.random() * 10}, () => "something");
// How is it possible to set this?
let lastSelectedInner: Inner | null = null;
</script>
{#each dynamicContent as item}
<Inner on:select = {(event) => lastSelectedInner = event.detail} />
{/each}
И Inner.svelte будет выглядеть примерно так:
<script lang = "ts>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
</script>
<button on:click = {() => dispatch("select", /* how do I forward the instance here? */)} >
<p>Click me</p>
</button>
Примечание. Мне нужно, чтобы фактический экземпляр находился в переменной, а не в индексе. Кроме того, я не хочу иметь массив всех Inner, а только одну ссылку на один компонент.
В моем случае использования довольно много реквизитов и функций дочернего элемента, которые должны быть доступны во внешнем компоненте, а внешний компонент может иметь сотни внутренних элементов, поэтому хранить массивы/индексы неэффективно. Мне также не нравится идея хранения свойства «индекс» во Inner, поскольку это нелогично с точки зрения инкапсуляции: Inner не нужно знать, что это один из многих, и не должно иметь значения, является ли он частью массива. или нет: логически он не имеет «индекса», это часть родительской структуры.



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


Судя по вашему комментарию, похоже, что экземпляру родительского компонента на самом деле не нужен доступ к экземпляру дочернего компонента, а только (некоторые) к данным в экземпляре дочернего компонента. Таким образом, экземпляр дочернего компонента может передавать эти данные в событии, а не сам экземпляр компонента. А может быть передача данных вообще не нужна?
Внешний.стройный
<script lang = "ts">
import Inner from "./Inner.svelte";
let items = [{
id: 1,
name: `a`,
}, {
id: 2,
name: `b`,
}]
let selectedItem: Object | null = null; // I don't know TS, so unsure about type ^^'
</script>
{#each items as item}
<Inner on:select = {(event) => selectedItem = item} />
{/each}
{#if selectedItem}
<h1>Selected</h1>
<div>Id: {selectedItem.id}</div>
<div>Name: {selectedItem.name}</div>
{/if}
Спасибо, но это не соответствует критериям, описанным в вопросе. Мне нужна фактическая ссылка на реальный дочерний компонент. Пример кода в вопросе очень прост и предназначен только для демонстрации минимальной необходимой настройки и конечной цели. Фактический вариант использования гораздо более сложен и, вероятно, мне не нужно его объяснять, но ясно одно: родительскому компоненту нужна ссылка на один из его динамически генерируемых дочерних компонентов. Вот в чем вопрос, и этот ответ полностью опускает это.
Если вы не хотите отредактировать или обновить ответ, чтобы он соответствовал критериям, могу ли я попросить вас удалить это, пожалуйста? Поскольку это не отвечает на вопрос, я бы предпочел, чтобы было ясно, что вопрос остается без ответа.
Насколько мне известно (но я не уверен), экземпляр компонента не может получить ссылку на себя (если только вы не обойдете это, используя родительский компонент нежелательным для вас способом). Итак, краткий ответ на ваш вопрос, вероятно, невозможен. Тогда предложение обходных путей - хороший ответ, ИМО. И, возможно, вы не ищете конкретное обходное решение, которое я предложил, но другие люди в будущем, которые думают, что у них такая же проблема, как и вы, и найдут этот вопрос, могут найти это обходное решение хорошим решением для себя. Но если вы не согласны, не стесняйтесь поставить минус, для этого и нужна кнопка.
Я бы не хотел ставить минус, поскольку вы прикладываете усилия, чтобы предложить ответ, и это просто кажется грубым, но, возможно, было бы хорошо начать ответ с чего-то вроде: короткий ответ «это невозможно из-за того, что Svelte не позволяет ..., но возможным обходным решением является ...", а не "вам, вероятно, это не нужно", поскольку сама необходимость зависит от вопроса, а предлагаемое решение представляет собой обходной путь с несколькими недостатками.
Насколько я знаю, дочерний компонент не может получить доступ к ссылке на себя, но его нужно будет создать через bind:this в родительском компоненте и передать как реквизит.
Это был бы способ установить lastSelectedInner без добавления дополнительной логики в компонент Inner.
<script>
import Inner from "./Inner.svelte";
/* placeholder array of 0-10 elements, to make it clear the number of Inners
* in this component is different depedning on some outside factors */
let dynamicContent = Array.from({length: Math.random() * 10}, () => "something");
let innerRefs = []
// How is it possible to set this?
let lastSelectedInner = null;
$: console.info(lastSelectedInner)
</script>
{#each dynamicContent as item, index}
<Inner bind:this = {innerRefs[index]}
on:select = {() => lastSelectedInner = innerRefs[index]} />
{/each}
Спасибо, я знал об этом подходе, но мне бы очень хотелось избежать хранения всех ссылок, поскольку их может быть довольно много. Возможно, я обусловлен такой оптимизацией языками более низкого уровня, и в браузере это имеет меньшее значение, но почему-то массив из тысячи элементов, необходимый для ситуации, когда на самом деле важен только один элемент, сбивает меня с толку. Есть ли где-нибудь в документации Svelte причины, по которым компонент не может ссылаться на себя?
После того, как было предложено несколько решений с хорошими обходными путями, я начал искать дополнительные варианты, которые полностью удовлетворяли бы требованию «отсутствия массива внутренних элементов» и «передачи фактической ссылки на компонент».
Я наткнулся на Svelte REPL (не знаю, кто автор), который показывает, как сделать это возможным с помощью специального элемента <svelte:self>. Вот изложенное там решение, адаптированное к этому вопросу. Inner.svelte:
<script lang = "ts">
export let outer = true;
export let component = null;
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
</script>
{#if outer}
<svelte:self
bind:this = {component}
outer = {false}
on:select = {() => dispatch("select", component)}
/>
{/if}
{#if !outer}
<button on:click = {() => dispatch("select")} >
<p>Click me</p>
</button>
{/if}
Этот подход не самый читаемый, поскольку он требует вложения внутреннего компонента внутрь себя и условного отображения содержимого только во вложенном компоненте. Тем не менее, он достигает именно того результата, который позволяет отправлять событие из компонента, который в деталях передает одну правильно типизированную ссылку.
Обновлено: обновлено в соответствии с комментарием @Corrl.
Есть ли у вас работающий Repl, использующий этот подход?
Я думаю, что его нужно настроить, чтобы он работал svelte.dev/repl/908de9703eb4472f8e744d3ca0a7f331?version=4.2.18
@Corrl Хорошая мысль, спасибо, что указали на это. Я написал это очень быстро и еще не проверял. Действительно, необходимо перенаправить событие внутреннего-внутреннего компонента во внешний-внутренний компонент (извините за путаницу в именах, переменные в REPL были названы так же, как и компоненты в моем вопросе).
Почему родительскому компоненту нужна ссылка на экземпляр дочернего компонента? Зачастую достаточно передать данные в экземпляр дочернего компонента с помощью реквизитов, и если родительскому элементу нужно каким-то образом изменить дочерний компонент, можно просто изменить реквизиты за один проход.