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方案更适合多组件共享数据的场景,能保证数据一致性,减少重复代码。
- 使用
derivedstore可以直接从currentLocation派生出ItemList,无需手动维护同步逻辑。
内容的提问来源于stack exchange,提问作者mapleweekend
相关产品推荐
相关产品推荐

