Svelte中store存储对象时响应式语句全量触发问题如何解决
Svelte 对 store 的响应式订阅默认是整个store值级别的,不会深度追踪对象内部的属性访问路径。你写$: $catalogue.currentBook, console.log(...)时,编译后实际是订阅整个catalogue store,只要store推送新值(无论修改哪个属性),所有引用了$catalogue的响应式块都会重新执行,因此会出现改一个属性触发全部关联响应式语句的问题。
另外你示例代码里直接写$catalogue.currentBook = xxx的更新方式本身也不符合Svelte store的更新规范:只有对$store整体赋值时才会触发store的set方法通知订阅者,直接修改内部属性属于可变副作用,触发更新属于编译层脏检查的偶然行为,不可靠。
方案1:用派生store拆分属性级订阅(推荐)
用Svelte内置的derived方法,从原对象store派生出每个属性对应的独立store,每个派生store只会在自身依赖的属性值变化时触发更新,完全符合Svelte的响应式设计逻辑:
<script> import { writable, derived } from "svelte/store"; const catalogue = writable({ currentBook: "War'n'Peace", currentFood: "Authentic Napoli frozen pizza" }); // 拆分独立属性派生store const currentBook = derived(catalogue, $c => $c.currentBook); const currentFood = derived(catalogue, $c => $c.currentFood); // 每个响应式块仅订阅对应派生store,不会互相干扰 $: $currentBook, console.log("Current book has changed"); $: $currentFood, console.log("Current food has changed"); // 更新时用不可变方式生成新对象,保证store能正确推送更新 function changeBook() { $catalogue = { ...$catalogue, currentBook: "Putin's People" }; } function changeFood() { $catalogue = { ...$catalogue, currentFood: "Authentic Valencia chorizo and mussels paella" }; } </script> <button on:click={changeBook}>Change book</button> <button on:click={changeFood}>Change food</button>
方案2:响应式块内手动做值比对
如果不想额外创建派生store,可以在响应式块内部缓存上一次的属性值,手动判断属性是否真的发生变更,再执行业务逻辑:
<script> import { writable } from "svelte/store"; const catalogue = writable({ currentBook: "War'n'Peace", currentFood: "Authentic Napoli frozen pizza" }); let lastBook, lastFood; $: { const newBook = $catalogue.currentBook; if (newBook !== lastBook) { lastBook = newBook; console.log("Current book has changed"); } } $: { const newFood = $catalogue.currentFood; if (newFood !== lastFood) { lastFood = newFood; console.log("Current food has changed"); } } function changeBook() { $catalogue = { ...$catalogue, currentBook: "Putin's People" }; } function changeFood() { $catalogue = { ...$catalogue, currentFood: "Authentic Valencia chorizo and mussels paella" }; } </script> <button on:click={changeBook}>Change book</button> <button on:click={changeFood}>Change food</button>
方案3:从数据结构层面拆分独立store
如果存放在同一个对象里的属性本身没有强联动需求,不需要作为整体读写,可以直接把每个属性拆成独立的顶层writable store,天然实现粒度隔离:
<script> import { writable } from "svelte/store"; const currentBook = writable("War'n'Peace"); const currentFood = writable("Authentic Napoli frozen pizza"); $: $currentBook, console.log("Current book has changed"); $: $currentFood, console.log("Current food has changed"); function changeBook() { $currentBook = "Putin's People"; } function changeFood() { $currentFood = "Authentic Valencia chorizo and mussels paella"; } </script> <button on:click={changeBook}>Change book</button> <button on:click={changeFood}>Change food</button>
注意:更新对象类型的store时,要遵循不可变更新原则,始终生成新的对象引用再赋值给
$store,不要直接修改原对象的内部属性,否则会出现订阅者收不到更新、响应式不触发的问题。
内容的提问来源于stack exchange,提问作者Nicolas Le Thierry d'Ennequin

