Почему массив записываемых файлов не обновляет должным образом значения внутри компонента Svelte?

У меня есть родительский шаблон (App.svelte), который создает массив доступных для записи и передает его компоненту (RedOrBlueOrPurple.svelte). Компонент перебирает элементы. Я хочу передать массив объектов для записи (а не создавать одну доступную для записи, содержащую весь массив объектов), потому что я хочу добавить subscribe обработчиков для каждого элемента индивидуально, когда что-то меняется внутри элемента. В противном случае обработчик подписки должен был бы выяснить, какой элемент был изменен, что, похоже, требует больше работы.

В приведенном ниже коде первая кнопка действительно изменяет фиолетовый атрибут, потому что за вызовом update следует refresh, который сбрасывает родительские значения. Я бы хотел избежать этого вызова, если это возможно, и просто использовать доступный для записи интерфейс, который понимают и могут использовать как родитель, так и ребенок.

Кроме того, мне не нравится, что шаблон должен использовать map на каждом, чтобы вытащить элемент, получить значение магазина (с помощью внутреннего вызова svelte get_store_value), но я не знаю, как бы я использовал {#each items as $items...} в противном случае (Я не могу понять, как использовать здесь $item). Есть ли более элегантный способ получить ценность?

Кроме того, и это кажется действительно прискорбным, тот факт, что консоль не отображает ошибки, когда я вызываю обновление и устанавливаю переменную const, кажется, что он нарушает правила JS и все еще компилируется. Я знаю, что Svelte аннотирует этот вызов под капотом, чтобы отреагировать на изменение, но это кажется неправильным.

Это здесь REPL: https://svelte.dev/repl/84fbae4358494844a79a7f119b084f01?version=3.19.2

введите здесь описание изображения

<script>
    import { writable } from 'svelte/store';
    import RedOrBlueOrPurple from './RedOrBlueOrPurple.svelte';

    const items = [];
    items.push( writable( { blue: true }));
    items.push( writable( { blue: false, red: true }));
    items.push( writable( { blue: true, red: true }));

const refresh = () => {
    items = items;
};

</script>

<RedOrBlueOrPurple {refresh} {items}/>

<script>
    import { get_store_value } from 'svelte/internal';
    export let items;
    export let refresh;

    const purple = (item, callRefresh) => {
        item.update( i => {
            i.purple = true;
            return i;
        });
        // We only call this for the first button, and if we don't, 
        // the items aren't updated, even though we do call update on
        // the store correctly. 
        if (callRefresh) {
            refresh();
        }
    };
</script>

{#each items.map( item => ({ item, value: get_store_value(item) })) as { item, value}, index }

<h1>
    Item : { index }
</h1>

<div>
RED: { value.red ? "Red" : "Not red"}
</div>
<div>
BLUE: { value.blue ? "Blue" : "Not blue"}
</div>

<div>
PURPLE: { value.purple ? "Purple" : "Not purple"}
</div>

<button on:click={ () => { purple(item, index === 0) } }>Change to purple</button>

{/each}

person xrd    schedule 06.03.2020    source источник


Ответы (2)


В вашем случае обновление экрана запускается только в том случае, если svelte обнаруживает присвоение items, которое вы используете в #each. Недостаточно просто обновить какой-либо элемент массива. Вы можете просто решить это так:

const purple = (item, callRefresh) => {
    item.update( i => {
        i.purple = true;
        return i;
    });
    items = items;  // instead of using the callback, simple reassign items here
};
person Andreas Dolk    schedule 07.03.2020
comment
Замечательно. Я все еще чувствую, что вызова update в магазине должно быть достаточно, чтобы получить реактивность, но, по крайней мере, теперь моему родителю не нужно ничего делать. - person xrd; 07.03.2020

Вы можете избежать этого, используя производное хранилище:

$: values = derived(items, x => x)

Итак, ваш код станет:

App.svelte

<script>
    import { writable } from 'svelte/store';
    import RedOrBlueOrPurple from './RedOrBlueOrPurple.svelte';

    let items = [];
    items.push( writable( { blue: true }));
    items.push( writable( { blue: false, red: true }));
    items.push( writable( { blue: true, red: true }));

    const more = () => {
        items = [writable({}), ...items]
    }
</script>

<!-- another button to ensure new items are processed correctly -->
<button on:click={more}>More</button>

<RedOrBlueOrPurple {items}/>

RedOrBlueOrPurple.svelte

<script>
    import { derived } from 'svelte/store'

    export let items = [];

    $: values = derived(items, x => x)

    const purple = (item, callRefresh) => {
        item.update(i => ({ ...i, purple: true }))
    };
</script>

{#each $values as value, index}
    <h1>Item: {index}</h1>
    <pre>{JSON.stringify(value)}</pre>
    <button on:click={() => {purple(items[index])}}>Change to purple</button>
{/each}

Обновленный REPL

Примечание

Не делайте этого:

import { get_store_value } from 'svelte/internal';

Существует эквивалент общедоступного API (см. документы):

import { get } from 'svelte/store'
person rixo    schedule 10.03.2020