You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Svelte变量未随localStorage更新,如何替代setInterval实现响应式?

问题

我开发了一款将数据存储在localStorage对应位置并在HTML中展示的Svelte应用,原本看中Svelte的响应式变量特性,但尝试让ItemList变量随localStorage.current_items变化时,该变量无法更新。目前只能通过setInterval轮询实现更新,这显然不是理想方案。同时我尝试使用writable store方法也没解决问题,请问如何实现ItemList随localStorage.current_items变化而正确更新?

原组件代码

<script lang="ts">
    import {
        getData,
        createItem,
        createLocation,
        closeItem,
    } from './lib/database.js';
    import LocationSelector from './lib/LocationSelector.svelte';
    import { flip } from 'svelte/animate';
    import { writable } from 'svelte/store';

    let DB = getData();
    // load items from localstorage.items

    let ItemList = [];
    let Location;

    setInterval(() => {
        Location = localStorage.current_items;
        ItemList = JSON.parse(localStorage.current_items).items;
    }, 500);

    console.log(ItemList);

    let newItem = '';
    let filter_showClosed = false;

    function addNewItem(e) {
        e.preventDefault();
        console.log(newItem);
        const newItemInput = document.querySelector(
            '#newItemInput'
        ) as HTMLInputElement;

        createItem(JSON.parse(Location).id, newItem);
        newItem = '';
    }

    function newItemKeyDown(e) {
        if (e.keyCode === 13) {
            addNewItem(e);
        }
    }
</script>

<LocationSelector />

<div class="app">
    <input
        type="text"
        id="newItemInput"
        bind:value={newItem}
        placeholder="Add a new item"
        on:keydown={newItemKeyDown}
    />
    <button
        id="filter_showClosed"
        data-active="false"
        on:click={function () {
            filter_showClosed = !filter_showClosed;
            let el = document.getElementById('filter_showClosed');
            if (filter_showClosed) {
                el.innerHTML = 'Hide closed';
                el.dataset.active = 'true';
            } else {
                el.innerHTML = 'Show closed';
                el.dataset.active = 'false';
            }
        }}>Show closed</button
    >
    <!-- <button
        id="deleteClosed"
        on:click={function () {
            let it = items;
            for (let i = 0; i < it.length; i++) {
                if (it[i].closed == true) {
                    it.splice(i, 1);
                }
            }
            items = it;
            sort_items(items);
        }}>Delete all closed</button
    > -->

    <div class="list">
        {#each ItemList as item, index (item.id)}
            <div class="item {item.closed}" animate:flip={{ duration: 100 }}>
                {#if item.closed == false || (filter_showClosed == true && item.closed == true)}
                    <div>
                        <img
                            src="/up.svg"
                            class="item-icon"
                            class:closed={item.closed == true}
                            alt="move item up in priority"
                            on:click={function () {
                                // increaseLevel({ item });
                            }}
                        />
                        {item.name} ({index})
                    </div>
                    <div>
                        {#if item.closed == false}
                            <img
                                src="/close.svg"
                                class="item-icon"
                                alt="close item"
                                on:click={function () {
                                    console.log(Location.id);
                                    closeItem(JSON.parse(Location).id, item.id);
                                }}
                            />
                        {/if}
                    </div>
                {/if}
            </div>
        {/each}
    </div>
</div>

<style>
</style>

尝试的writable store代码

import { writable } from 'svelte/store';

const ItemList = writable([]);
let Location = {};

let newItem = '';
let filter_showClosed = false;

function addNewItem(e) {
    e.preventDefault();
    console.log(newItem);
    const newItemInput = document.querySelector(
        '#newItemInput'
    ) as HTMLInputElement;

    createItem(Location.id, newItem);
    newItem = '';
}

function newItemKeyDown(e) {
    if (e.keyCode === 13) {
        addNewItem(e);
    }
}

// Update the Location object with the current value of localStorage.current_items as an object
Location = JSON.parse(localStorage.current_items);

// Update the ItemList store with the new location's items
ItemList.set(Location.items);
解决方案

核心问题是localStorage本身不会触发Svelte的响应式更新,需要手动监听其变化并同步到变量或store中,以下是两种可行方案:

方案一:监听storage事件(组件内直接实现)

直接在组件中监听window的storage事件,同时处理同页面内的localStorage修改(原生storage事件仅监听跨页面变化):

<script lang="ts">
    import { onMount } from 'svelte';
    import {
        getData,
        createItem,
        createLocation,
        closeItem,
    } from './lib/database.js';
    import LocationSelector from './lib/LocationSelector.svelte';
    import { flip } from 'svelte/animate';

    let DB = getData();
    // 初始化数据
    let Location = JSON.parse(localStorage.current_items || '{}');
    let ItemList = Location.items || [];

    // 处理localStorage变化
    function handleStorageChange(e: StorageEvent) {
        if (e.key === 'current_items' && e.newValue) {
            Location = JSON.parse(e.newValue);
            ItemList = Location.items || [];
        }
    }

    onMount(() => {
        // 添加跨页面storage监听
        window.addEventListener('storage', handleStorageChange);

        // 重写setItem,处理同页面内的修改
        const originalSetItem = localStorage.setItem;
        localStorage.setItem = function(key, value) {
            const event = new StorageEvent('storage', {
                key,
                newValue: value,
                oldValue: localStorage.getItem(key)
            });
            window.dispatchEvent(event);
            return originalSetItem.apply(this, arguments);
        };

        // 组件销毁时清理监听
        return () => {
            window.removeEventListener('storage', handleStorageChange);
            localStorage.setItem = originalSetItem;
        };
    });

    let newItem = '';
    let filter_showClosed = false;

    function addNewItem(e) {
        e.preventDefault();
        Location.id && createItem(Location.id, newItem);
        newItem = '';
    }

    function newItemKeyDown(e) {
        if (e.keyCode === 13) {
            addNewItem(e);
        }
    }
</script>

<!-- 模板部分保持不变 -->
<LocationSelector />
<div class="app">
    <!-- ... 原模板内容 ... -->
</div>

<style>
</style>

方案二:封装自定义localStorage store(优雅复用)

创建自动同步localStorage的自定义store,适合多组件共享数据场景:

第一步:创建store文件(src/lib/localStorageStore.ts)

import { writable, derived } from 'svelte/store';

export function localStorageStore<T>(key: string, initialValue: T) {
    // 从localStorage读取初始值
    const storedValue = localStorage.getItem(key);
    const value = storedValue ? JSON.parse(storedValue) : initialValue;

    const store = writable<T>(value);

    // 订阅store变化,同步到localStorage
    store.subscribe((newValue) => {
        localStorage.setItem(key, JSON.stringify(newValue));
    });

    // 监听跨页面storage变化
    window.addEventListener('storage', (e) => {
        if (e.key === key && e.newValue) {
            store.set(JSON.parse(e.newValue));
        }
    });

    // 处理同页面内的localStorage修改
    const originalSetItem = localStorage.setItem;
    localStorage.setItem = function(k, v) {
        if (k === key) {
            store.set(JSON.parse(v));
        }
        return originalSetItem.apply(this, arguments);
    };

    return store;
}

第二步:在组件中使用自定义store

<script lang="ts">
    import { derived } from 'svelte/store';
    import {
        getData,
        createItem,
        createLocation,
        closeItem,
    } from './lib/database.js';
    import LocationSelector from './lib/LocationSelector.svelte';
    import { flip } from 'svelte/animate';
    import { localStorageStore } from './lib/localStorageStore';

    let DB = getData();
    // 创建同步localStorage.current_items的store
    const currentLocation = localStorageStore('current_items', { id: '', items: [] });
    // 派生ItemList,自动同步currentLocation的items
    const ItemList = derived(currentLocation, loc => loc.items || []);

    let newItem = '';
    let filter_showClosed = false;

    function addNewItem(e) {
        e.preventDefault();
        $currentLocation.id && createItem($currentLocation.id, newItem);
        newItem = '';
    }

    function newItemKeyDown(e) {
        if (e.keyCode === 13) {
            addNewItem(e);
        }
    }
</script>

<LocationSelector />
<div class="app">
    <input
        type="text"
        id="newItemInput"
        bind:value={newItem}
        placeholder="Add a new item"
        on:keydown={newItemKeyDown}
    />
    <button
        id="filter_showClosed"
        data-active={filter_showClosed}
        on:click={() => {
            filter_showClosed = !filter_showClosed;
        }}>{filter_showClosed ? 'Hide closed' : 'Show closed'}</button
    >

    <div class="list">
        <!-- 使用$前缀访问store的值 -->
        {#each $ItemList as item, index (item.id)}
            <div class="item {item.closed}" animate:flip={{ duration: 100 }}>
                {#if !item.closed || (filter_showClosed && item.closed)}
                    <div>
                        <img
                            src="/up.svg"
                            class="item-icon"
                            class:closed={item.closed}
                            alt="move item up in priority"
                            on:click={() => {
                                // increaseLevel({ item });
                            }}
                        />
                        {item.name} ({index})
                    </div>
                    <div>
                        {#if !item.closed}
                            <img
                                src="/close.svg"
                                class="item-icon"
                                alt="close item"
                                on:click={() => {
                                    $currentLocation.id && closeItem($currentLocation.id, item.id);
                                }}
                            />
                        {/if}
                    </div>
                {/if}
            </div>
        {/each}
    </div>
</div>

<style>
</style>

关键说明

  • 原生storage事件仅监听不同页面间的localStorage修改,所以需要重写localStorage.setItem来触发同页面内的更新事件。
  • 自定义store方案更适合多组件共享数据的场景,能保证数据一致性,减少重复代码。
  • 使用derived store可以直接从currentLocation派生出ItemList,无需手动维护同步逻辑。

内容的提问来源于stack exchange,提问作者mapleweekend

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 06:55:16