mirror of https://github.com/sveltejs/svelte
fix: freeze effects-inside-deriveds when disconnecting, unfreeze on reconnect (#17682)
This supersedes #16595, and fixes the issue by 'freezing' effects inside deriveds when those deriveds are disconnected, and unfreezing them when they reconnect. This is preferable to the current asymmetric behaviour on `main` (in which effects are destroyed when the derived is disconnected, and never recreated) and #16595, which causes the derived itself to be re-evaluated. ### Before submitting the PR, please make sure you do the following - [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`. - [x] This message body should clearly illustrate what problems it solves. - [x] Ideally, include a test that fails without this PR but passes with it. - [x] If this PR changes code within `packages/svelte/src`, add a changeset (`npx changeset`). ### Tests and linting - [x] Run the tests with `pnpm test` and lint the project with `pnpm lint`pull/17690/head
parent
9400689e1c
commit
4eee2f7c92
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: freeze effects-inside-deriveds when disconnecting, unfreeze on reconnect
|
||||
@ -0,0 +1,42 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
test({ assert, target, logs }) {
|
||||
const [toggle, run] = target.querySelectorAll('button');
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
'<button>toggle</button><button>run</button><p>hello: 0</p>'
|
||||
);
|
||||
|
||||
flushSync(() => run.click());
|
||||
assert.deepEqual(logs, []);
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
'<button>toggle</button><button>run</button><p>hello: 1</p>'
|
||||
);
|
||||
|
||||
flushSync(() => toggle.click());
|
||||
assert.deepEqual(logs, ['aborted']);
|
||||
assert.htmlEqual(target.innerHTML, '<button>toggle</button><button>run</button>');
|
||||
|
||||
flushSync(() => run.click());
|
||||
flushSync(() => run.click());
|
||||
flushSync(() => run.click());
|
||||
|
||||
flushSync(() => toggle.click());
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
'<button>toggle</button><button>run</button><p>hello: 1</p>'
|
||||
);
|
||||
|
||||
flushSync(() => run.click());
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
'<button>toggle</button><button>run</button><p>hello: 2</p>'
|
||||
);
|
||||
|
||||
assert.deepEqual(logs, ['aborted']);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,53 @@
|
||||
<script>
|
||||
import { getAbortSignal } from 'svelte';
|
||||
|
||||
const callbacks = new Map();
|
||||
|
||||
// similar semantics to setInterval, but simpler to test
|
||||
function add(fn) {
|
||||
const id = crypto.randomUUID();
|
||||
callbacks.set(id, fn);
|
||||
return id;
|
||||
}
|
||||
|
||||
function remove(id) {
|
||||
callbacks.delete(id);
|
||||
}
|
||||
|
||||
function run() {
|
||||
for (const fn of callbacks.values()) {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
class Timer {
|
||||
constructor(text) {
|
||||
this.elapsed = $state(0);
|
||||
this.text = $derived(text + ': ' + this.elapsed);
|
||||
|
||||
$effect(() => {
|
||||
const id = add(() => {
|
||||
this.elapsed += 1;
|
||||
});
|
||||
|
||||
getAbortSignal().onabort = () => {
|
||||
console.log('aborted');
|
||||
};
|
||||
|
||||
return () => remove(id);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let timer = $derived(new Timer('hello'));
|
||||
|
||||
let visible = $state(true);
|
||||
</script>
|
||||
|
||||
<button onclick={() => visible = !visible}>toggle</button>
|
||||
<button onclick={run}>run</button>
|
||||
|
||||
{#if visible}
|
||||
<p>{timer.text}</p>
|
||||
{/if}
|
||||
Loading…
Reference in new issue