简体   繁体   English

为什么可写数组不能正确更新 Svelte 组件内的值?

[英]Why doesn't an array of writables properly update values inside the Svelte component?

I have a parent template (App.svelte) which creates an array of writables and passes it to a component ( RedOrBlueOrPurple.svelte ).我有一个父模板 (App.svelte),它创建一个可写数组并将其传递给一个组件 ( RedOrBlueOrPurple.svelte )。 The component iterates over the items.该组件迭代项目。 I want to pass an array of writables (rather than creating a single writable that holds the entire array of objects) because I want to add subscribe handlers on each item individually when things change inside an item.我想传递一个可写数组(而不是创建一个包含整个对象数组的可写对象),因为我想在项目内部发生变化时分别在每个项目上添加subscribe处理程序。 Otherwise, my subscribe handler would need to figure out which item was changed which seems like more work.否则,我的订阅处理程序需要弄清楚哪个项目被更改,这似乎需要更多的工作。

In this code below, the first button does effectively change the purple attribute because that call to update is followed by a refresh which resets the parent values.在下面的这段代码中,第一个按钮确实有效地更改了紫色属性,因为调用update之后是refresh ,从而重置父值。 I would love to avoid this call if possible and just use the writable as the interface which both the parent and child understand and can use.如果可能的话,我很想避免这个调用,只使用可写的接口作为父母和孩子都理解和可以使用的接口。

Also, I don't love that the template has to use a map on the each to pull out the item, get the value of the store (using the svelte internal call get_store_value ) but I don't know how I would use {#each items as $items...} otherwise (I cannot figure out how to use $item here).此外,我不喜欢模板必须使用每个map上的map来拉出项目,获取商店的价值(使用 svelte 内部调用get_store_value ),但我不知道我将如何使用{#each items as $items...}否则(我不知道如何在这里使用$item )。 Is there a more elegant way of getting the value?有没有更优雅的方式来获取价值?

Also, and this seems really unfortunate, the fact that the console does not display errors when I call refresh and set a const variable seems like it breaks JS rules and still compiles.此外,这似乎真的很不幸,当我调用 refresh 并设置一个const变量时,控制台不显示错误的事实似乎违反了 JS 规则并且仍然可以编译。 I know the Svelte is annotating this call underneath the hood to react to the change, but this seems wrong.我知道 Svelte 正在幕后注释此调用以对更改做出反应,但这似乎是错误的。

This is at this REPL here: https://svelte.dev/repl/84fbae4358494844a79a7f119b084f01?version=3.19.2这是在这里的 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}

The screen update is only triggered, in your case, if svelte detects an assignment to items , which you use in the #each .在您的情况下,仅当 svelte 检测到您在#each使用的items分配时才会触发屏幕更新。 Just updating any of the array's items is not enough.仅更新数组的任何项目是不够的。 You could simply solve it like this:你可以像这样简单地解决它:

const purple = (item, callRefresh) => {
    item.update( i => {
        i.purple = true;
        return i;
    });
    items = items;  // instead of using the callback, simple reassign items here
};

You can get away with this by using a derived store:您可以通过使用派生商店来解决这个问题:

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

So your code would become:所以你的代码会变成:

App.svelte 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红色或蓝色或紫色.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}

Updated REPL 更新的 REPL

Note笔记

Don't:别:

import { get_store_value } from 'svelte/internal';

There's a public API equivalent (see docs ):有一个公共 API 等价物(参见文档):

import { get } from 'svelte/store'

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM