首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么派生Svelte存储在使用‘$` vs `订阅’时有不同的行为?

为什么派生Svelte存储在使用‘$` vs `订阅’时有不同的行为?
EN

Stack Overflow用户
提问于 2022-06-29 17:51:18
回答 1查看 211关注 0票数 3

我有一个数据模型,我不能在这个项目中改变。我正在尝试删除并简化下面的示例代码,因此希望这与我试图重新生成的内容仍然有意义。

假设我有两家商店。一家商店存放“容器”,另一家商店存放“物品”--每个商店在整个应用程序中都被独立地用于各种用途。默认情况下,容器只保存项ID。有时,我希望将数据去规范化,以便在某些页面上使用。我使用派生存储并对每个容器对象进行去denormalize,并将它们转换为DenormalizedContainers。

假设我有一个用例,我想要创建一个新的Item对象,然后将它添加到给定的容器中。据我所知,这将导致派生存储更新两次(一次是对项的更改,另一次是对容器的更改),因此,任何使用该派生存储的内容都将更新两次(取决于调用方式)。

为什么这种行为会因subscribe而异于$

而且,虽然这对我来说不是一个大问题,但我只是好奇是否有任何原生的Svelte-y方法来解决这个问题而不改变数据模型(同时仍然能够使用subscribe API?

代码语言:javascript
复制
<script lang="ts">
    import { derived, writable } from "svelte/store";

    type Container = {
        id: string;
        itemIds: string[];
    };

    type Item = {
        id: string;
        name: string;
    };

    type DenormalizedContainer = {
        id: string;
        items: Item[];
    };

    const containers = writable<Container[]>([]);
    const items = writable<Item[]>([]);

    const denormalizedContainers = derived(
        [containers, items],
        ([$containers, $items]): DenormalizedContainer[] => {
            return $containers.map(({ id, itemIds }) => {
                const denormalizedContainer = {
                    id,
                    items: itemIds.map((id) =>
                        $items.find((item) => {
                            item.id === id;
                        })
                    ),
                };
                return denormalizedContainer;
            });
        }
    );

    function handleAddContainer() {
        const value = Math.random() * 1000;
        const newContainer: Container = { id: `new-container-${value}`, itemIds: [] };
        containers.set([...$containers, newContainer]);
    }
    function handleAddItem() {
        const value = Math.random() * 1000;
        const newItem: Item = { id: `new-id-${value}`, name: `new-name-${value}` };
        items.set([...$items, newItem]);
    }
    function handleAddBoth() {
        const value = Math.random() * 1000;
        const newItem: Item = { id: `new-id-${value}`, name: `new-name-${value}` };
        const newContainer: Container = { id: `new-container-${value}`, itemIds: [newItem.id] };
        items.set([...$items, newItem]);
        containers.set([...$containers, newContainer]);
    }

    $: console.log(`$: There are ${$containers.length} containers`);
    $: console.log(`$: There are ${$items.length} items`);
    $: console.log(`$: There are ${$denormalizedContainers.length} denormalized containers`);

    denormalizedContainers.subscribe((newValue) =>
        console.log(
            `Subscribe: There are ${$denormalizedContainers.length} denormalized containers`
        )
    );
</script>

<button class="block" on:click={() => handleAddContainer()}>Add container</button>
<button class="block" on:click={() => handleAddItem()}>Add item</button>
<button class="block" on:click={() => handleAddBoth()}>Add container and item</button>

结果:

单击每个按钮一次,以下是日志:

页面加载:

代码语言:javascript
复制
Subscribe: There are 0 denormalized containers
$: There are 0 containers
$: There are 0 items
$: There are 0 denormalized containers

添加容器:

代码语言:javascript
复制
Subscribe: There are 1 denormalized containers
$: There are 1 containers
$: There are 1 denormalized containers

增加项目:

代码语言:javascript
复制
Subscribe: There are 1 denormalized containers
$: There are 1 items
$: There are 1 denormalized containers

加两项:

代码语言:javascript
复制
Subscribe: There are 1 denormalized containers
Subscribe: There are 2 denormalized containers
$: There are 2 containers
$: There are 2 items
$: There are 2 denormalized containers

通过$处理的更新一直是我想要的,但出于某种原因,手动subscribe API显示了两个更新。我在.svelte文件之外有几个位置,我需要在那里使用subscribe API。

参考文献:

类似于此,但并没有解释为什么$的行为与subscribe不同。

Svelte派生的存储库原子/取消更新

可能是根本的问题?

https://github.com/sveltejs/svelte/issues/6730

可能在3.1/3.2中解释过。微任务将反应性语句捆绑在一起,并在勾尾应用它们。而subscribe则实时处理更新。注意:将我的subscribe代码封装在tick中只会使它使用相同的数据运行两次。

https://dev.to/isaachagoel/svelte-reactivity-gotchas-solutions-if-you-re-using-svelte-in-production-you-should-read-this-3oj3

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-06-29 19:28:22

如果您查看编译后的输出,可以看到在update上触发了反应性语句,每个事件循环应该只发生一次。

代码语言:javascript
复制
    $$self.$$.update = () => {
        if ($$self.$$.dirty & /*$containers*/ 256) {
            $: console.log(`$: There are ${$containers.length} containers`);
        }

        if ($$self.$$.dirty & /*$items*/ 128) {
            $: console.log(`$: There are ${$items.length} items`);
        }

        if ($$self.$$.dirty & /*$denormalizedContainers*/ 64) {
            $: console.log(`$: There are ${$denormalizedContainers.length} denormalized containers`);
        }
    };

您可以在事件循环中创建自己的绑定更新的方法。例如使用微任务

代码语言:javascript
复制
function debouncedSubscribe(store, callback) {
    let key;
    return store.subscribe(value => {
        key = {};
        const currentKey = key;
        queueMicrotask(() => {
            if (key == currentKey)
                callback(value);
        });
    });
}

debouncedSubscribe(denormalizedContainers, value =>
    console.log(
        `Subscribe: There are ${value.length} denormalized containers`
    )
);

REPL实例

没有办法再次取消微任务,所以旧的任务会通过一个键失效。确保只执行最后一个任务的其他方法可能更优雅。

对于setTimeout,也可以这样做,并且可以取消前一个延迟,但是最小延迟会更大:

代码语言:javascript
复制
function debouncedSubscribe(store, callback) {
    let timeout;
    return store.subscribe(value => {
        clearTimeout(timeout);
        timeout = setTimeout(() => callback(value));
    });
}
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72805846

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档