preserve DIRTY/MAYBE_DIRTY status of deferred effects

pull/16487/head
Rich Harris 1 month ago
parent a826136d74
commit 0134d04c16

@ -10,7 +10,8 @@ import {
INERT,
RENDER_EFFECT,
ROOT_EFFECT,
USER_EFFECT
USER_EFFECT,
MAYBE_DIRTY
} from '#client/constants';
import { async_mode_flag } from '../../flags/index.js';
import { deferred, define_property } from '../../shared/utils.js';
@ -146,6 +147,18 @@ export class Batch {
*/
#block_effects = [];
/**
* Deferred effects (which run after async work has completed) that are DIRTY
* @type {Effect[]}
*/
#dirty_effects = [];
/**
* Deferred effects that are MAYBE_DIRTY
* @type {Effect[]}
*/
#maybe_dirty_effects = [];
/**
* A set of branches that still exist, but will be destroyed when this batch
* is committed we skip over these during `process`
@ -221,10 +234,9 @@ export class Batch {
this.#deferred?.resolve();
} else {
// otherwise mark effects clean so they get scheduled on the next run
for (const e of this.#render_effects) set_signal_status(e, CLEAN);
for (const e of this.#effects) set_signal_status(e, CLEAN);
for (const e of this.#block_effects) set_signal_status(e, CLEAN);
this.#defer_effects(this.#render_effects);
this.#defer_effects(this.#effects);
this.#defer_effects(this.#block_effects);
}
if (current_values) {
@ -307,6 +319,21 @@ export class Batch {
}
}
/**
* @param {Effect[]} effects
*/
#defer_effects(effects) {
for (const e of effects) {
const target = (e.f & DIRTY) !== 0 ? this.#dirty_effects : this.#maybe_dirty_effects;
target.push(e);
// mark as clean so they get scheduled if they depend on pending async state
set_signal_status(e, CLEAN);
}
effects.length = 0;
}
/**
* Associate a change to a given source with the current
* batch, noting its previous and current values
@ -384,18 +411,13 @@ export class Batch {
this.#pending -= 1;
if (this.#pending === 0) {
for (const e of this.#render_effects) {
for (const e of this.#dirty_effects) {
set_signal_status(e, DIRTY);
schedule_effect(e);
}
for (const e of this.#effects) {
set_signal_status(e, DIRTY);
schedule_effect(e);
}
for (const e of this.#block_effects) {
set_signal_status(e, DIRTY);
for (const e of this.#maybe_dirty_effects) {
set_signal_status(e, MAYBE_DIRTY);
schedule_effect(e);
}

@ -0,0 +1,28 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
await tick();
const [increment] = target.querySelectorAll('button');
assert.deepEqual(logs, [false]);
assert.htmlEqual(target.innerHTML, '<button>increment</button><p>0</p>');
increment.click();
await tick();
assert.deepEqual(logs, [false]);
assert.htmlEqual(target.innerHTML, '<button>increment</button><p>1</p>');
increment.click();
await tick();
assert.deepEqual(logs, [false, true]);
assert.htmlEqual(target.innerHTML, '<button>increment</button><p>2</p>');
increment.click();
await tick();
assert.deepEqual(logs, [false, true]);
assert.htmlEqual(target.innerHTML, '<button>increment</button><p>3</p>');
}
});

@ -0,0 +1,17 @@
<script lang="ts">
let count = $state(0);
let two_or_larger = $derived(count >= 2);
$effect(() => {
console.log(two_or_larger);
});
</script>
<svelte:boundary>
<button onclick={() => count += 1}>increment</button>
<p>{await count}</p>
{#snippet pending()}
<p>loading...</p>
{/snippet}
</svelte:boundary>
Loading…
Cancel
Save