javascript - ทำไม Svelte store ที่สืบทอดมาถึงมีพฤติกรรมต่างกันเมื่อใช้ `$` เทียบกับ `subscribe`
ฉันมีโมเดลข้อมูลที่ฉันไม่สามารถเปลี่ยนแปลงในโครงการนี้ได้ ฉันกำลังพยายามลดความซับซ้อนและทำให้โค้ดตัวอย่างด้านล่างง่ายขึ้น ดังนั้นหวังว่านี่จะยังคงเข้าใจได้กับสิ่งที่ฉันกำลังพยายามสร้างใหม่
สมมติว่าฉันมีร้านสองร้าน ร้านหนึ่งเก็บ "containers" และอีกร้านหนึ่งเก็บ "items" - แต่ละร้านถูกใช้งานอย่างอิสระทั่วทั้งแอปเพื่อวัตถุประสงค์ต่างๆ Container โดยค่าเริ่มต้นจะเก็บแค่ Item ID เป็นบางครั้งฉันต้องการทำให้ข้อมูลไม่เป็นปกติเพื่อใช้งานบนบางหน้า ฉันจึงใช้ร้านแบบ derived และทำให้แต่ละออบเจ็กต์ของ container ไม่เป็นปกติ เปลี่ยนพวกมันเป็น DenormalizedContainers
สมมติว่าฉันมีกรณีการใช้งานที่ฉันต้องการสร้างวัตถุ Item ใหม่ จากนั้นเพิ่มมันเข้าไปใน Container ที่กำหนด ตามที่ฉันเข้าใจ นั่นจะทำให้ store ที่สืบทอดอัพเดตสองครั้ง (ครั้งหนึ่งสำหรับการเปลี่ยนแปลงใน items และอีกครั้งสำหรับการเปลี่ยนแปลงใน containers) และดังนั้นสิ่งใดก็ตามที่ใช้ store ที่สืบทอดนั้นจะอัพเดตสองครั้ง (ขึ้นอยู่กับวิธีการเรียกใช้งาน)
ทำไมพฤติกรรมถึงจะแตกต่างจาก subscribe ไปยัง $?
นอกจากนี้ แม้ว่ามันจะไม่ใช่ปัญหาใหญ่สำหรับผม ผมก็แค่สงสัยว่ามีวิธีแบบ Svelte ดั้งเดิมในการแก้ปัญหานี้โดยไม่ต้องเปลี่ยนโมเดลข้อมูลหรือไม่ (ในขณะที่ยังสามารถใช้ API subscribe ได้)?
<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>
ผลลัพธ์ :
เมื่อคลิกแต่ละปุ่มเพียงครั้งเดียว นี่คือล็อก:
การโหลดหน้า:
Subscribe: There are 0 denormalized containers
$: There are 0 containers
$: There are 0 items
$: There are 0 denormalized containers
เพิ่มคอนเทนเนอร์:
Subscribe: There are 1 denormalized containers
$: There are 1 containers
$: There are 1 denormalized containers
เพิ่มรายการ:
Subscribe: There are 1 denormalized containers
$: There are 1 items
$: There are 1 denormalized containers
เพิ่มทั้งสอง:
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
การอัปเดตที่จัดการผ่าน $ คือสิ่งที่ฉันต้องการตลอดเวลา แต่ด้วยเหตุผลบางอย่าง API subscribe แบบแมนนวลจะแสดงการอัปเดตสองครั้ง ฉันมีตำแหน่งสองสามแห่งนอกไฟล์ .svelte ซึ่งฉันจะต้องใช้ API subscribe
เอกสารอ้างอิง :
คล้ายกับสิ่งนี้ แต่ไม่ได้อธิบายว่าทำไม $ จึงทำงานต่างจาก subscribe
Svelte Derived Store การอัปเดตแบบอะตอมิก/แบบดีเลย์
อาจจะเป็นปัญหาพื้นฐานหรือไม่?
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
คำตอบ #1
ถ้าคุณดูที่ผลลัพธ์ที่คอมไพล์แล้ว คุณจะเห็นว่าคำสั่งแบบ reactive ถูกกระตุ้นใน update ซึ่งควรเกิดขึ้นเพียงครั้งเดียวต่อหนึ่งรอบเหตุการณ์
$$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`);
}
};
คุณสามารถสร้างวิธีการรวมการอัปเดตของคุณเองในลูปเหตุการณ์ เช่น การใช้ไมโครทาส์:
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 สามารถทำได้เช่นกัน และด้วยวิธีนั้นสามารถยกเลิกอันก่อนหน้าได้ แต่เวลาหน่วงขั้นต่ำจะมากกว่า:
function debouncedSubscribe(store, callback) {
let timeout;
return store.subscribe(value => {
clearTimeout(timeout);
timeout = setTimeout(() => callback(value));
});
}
