Svelte如何响应数据存储中字典变量的变更?
问题描述
我正尝试让我的Svelte应用响应数据存储(data-store)中定义的字典的变更。创建新条目时会调用createNewItem函数,该函数内的日志显示user store已更新,但尽管user.options已明确变更,getOptions函数却未被调用。
代码示例
App.svelte
<select bind:value={selectedOption}> {#each user_options as option} <option value={option}>{option}</option> {/each} </select> <button on:click={addNewItem}>New item</button> <NewItem /> <script> import {user, state, elementDisplay } from './data' import NewItem from "./NewItem.svelte"; $: user_options = getOptions($user.options); $: new_item = createNewItem($state.new_item); let selectedOption = ""; function getOptions(user_options) { console.log('user changed') let options = [] if (state.new_item != '') { user_options[$user.new_item] = '' } for (const [option, value] of Object.entries(user_options)) { options.push(option) } return options } function createNewItem(new_item) { if (new_item.length > 0 ) { console.log('ante', $user) $user.options[new_item] = ''; console.log('post', $user) } } function addNewItem() { elementDisplay('new-item', 'block') document.getElementById('new-item-value').focus(); } </script>
NewItem.svelte
<div id="new-item"> <input type="text" id="new-item-value"> <div class="buttons"> <button class="select-button" on:click={useItem}>Use</button> <button class="select-button" on:click={close}>Close</button> </div> </div> <style> #new-item { display: none; } </style> <script> import { state, elementDisplay } from './data' function useItem() { let input_field = document.getElementById('new-item-value') if (input_field.value) { $state.new_item = input_field.value } close() } function close() { elementDisplay('new-item', 'none'); } </script>
data.js
import { writable } from 'svelte/store'; export let user = writable({ new_item: '', options: { "2": "Option 2", "1": "Option 1", "3": "Option 3", } } ); export let state = writable({ new_item: '', }); export function elementDisplay(item_name, display) { let item = document.getElementById(item_name); item.style.display = display; };
问题原因与解决方案
核心问题
Svelte的writable store仅在引用发生变化时才会触发订阅更新。你在createNewItem里直接修改$user.options的属性:
$user.options[new_item] = '';
这属于原地修改对象,user store的引用没有改变,因此不会触发依赖$user的反应式语句(也就是$: user_options = getOptions($user.options))执行,自然getOptions不会被调用。
修复方案
你需要创建新的对象来替换原有的user对象,让store检测到引用变化:
方案1:使用展开运算符重新赋值
修改createNewItem函数:
function createNewItem(new_item) { if (new_item.length > 0 ) { console.log('ante', $user) // 创建新的options对象,添加新条目 const newOptions = { ...$user.options, [new_item]: '' }; // 创建新的user对象替换原对象 $user = { ...$user, options: newOptions }; console.log('post', $user) } }
方案2:使用store的update方法
也可以直接调用store的update方法,在回调中返回新的状态:
function createNewItem(new_item) { if (new_item.length > 0 ) { console.log('ante', $user) user.update(currentUser => { return { ...currentUser, options: { ...currentUser.options, [new_item]: '' } }; }); console.log('post', $user) } }
额外优化
在getOptions函数中,你直接修改了传入的user_options参数(也就是$user.options的引用),这会导致store里的对象被原地修改,同样可能引发更新问题。建议创建新数组时不要修改原对象:
function getOptions(user_options) { console.log('user changed') let options = []; // 创建新的options对象,避免修改原store数据 const tempOptions = { ...user_options }; if ($state.new_item != '') { tempOptions[$state.new_item] = ''; } for (const [option] of Object.entries(tempOptions)) { options.push(option); } return options; }
内容的提问来源于stack exchange,提问作者Psionman
相关产品推荐
相关产品推荐

