chore: verify that `$effect.root(...)` does not re-run (#11020)

pull/11027/head
Rich Harris 12 months ago committed by GitHub
parent 0a162924fb
commit 34748ba015
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -53,6 +53,7 @@ export function push_effect(effect, parent_effect) {
*/ */
function create_effect(type, fn, sync) { function create_effect(type, fn, sync) {
var is_root = (type & ROOT_EFFECT) !== 0; var is_root = (type & ROOT_EFFECT) !== 0;
/** @type {import('#client').Effect} */ /** @type {import('#client').Effect} */
var effect = { var effect = {
ctx: current_component_context, ctx: current_component_context,
@ -150,9 +151,7 @@ export function user_pre_effect(fn) {
* @returns {() => void} * @returns {() => void}
*/ */
export function effect_root(fn) { export function effect_root(fn) {
// TODO is `untrack` correct here? Should `fn` re-run if its dependencies change? const effect = create_effect(ROOT_EFFECT, fn, true);
// Should it even be modelled as an effect?
const effect = create_effect(ROOT_EFFECT, () => untrack(fn), true);
return () => { return () => {
destroy_effect(effect); destroy_effect(effect);
}; };

@ -245,27 +245,25 @@ function _mount(
const unmount = effect_root(() => { const unmount = effect_root(() => {
branch(() => { branch(() => {
untrack(() => { if (context) {
if (context) { push({});
push({}); var ctx = /** @type {import('#client').ComponentContext} */ (current_component_context);
var ctx = /** @type {import('#client').ComponentContext} */ (current_component_context); ctx.c = context;
ctx.c = context; }
}
if (events) {
if (events) { // We can't spread the object or else we'd lose the state proxy stuff, if it is one
// We can't spread the object or else we'd lose the state proxy stuff, if it is one /** @type {any} */ (props).$$events = events;
/** @type {any} */ (props).$$events = events; }
}
should_intro = intro;
should_intro = intro; // @ts-expect-error the public typings are not what the actual function looks like
// @ts-expect-error the public typings are not what the actual function looks like component = Component(anchor, props) || {};
component = Component(anchor, props) || {}; should_intro = true;
should_intro = true;
if (context) {
if (context) { pop();
pop(); }
}
});
}); });
return () => { return () => {

@ -21,7 +21,8 @@ import {
INERT, INERT,
BRANCH_EFFECT, BRANCH_EFFECT,
STATE_SYMBOL, STATE_SYMBOL,
BLOCK_EFFECT BLOCK_EFFECT,
ROOT_EFFECT
} from './constants.js'; } from './constants.js';
import { flush_tasks } from './dom/task.js'; import { flush_tasks } from './dom/task.js';
import { add_owner } from './dev/ownership.js'; import { add_owner } from './dev/ownership.js';
@ -692,7 +693,7 @@ export function get(signal) {
// Register the dependency on the current reaction signal. // Register the dependency on the current reaction signal.
if ( if (
current_reaction !== null && current_reaction !== null &&
(current_reaction.f & BRANCH_EFFECT) === 0 && (current_reaction.f & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 &&
!current_untracking !current_untracking
) { ) {
const unowned = (current_reaction.f & UNOWNED) !== 0; const unowned = (current_reaction.f & UNOWNED) !== 0;
@ -741,6 +742,7 @@ export function get(signal) {
update_derived(/** @type {import('./types.js').Derived} **/ (signal), false); update_derived(/** @type {import('./types.js').Derived} **/ (signal), false);
} }
} }
return signal.v; return signal.v;
} }

@ -0,0 +1,31 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
import { log } from './log.js';
export default test({
before_test() {
log.length = 0;
},
async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button');
flushSync(() => {
b1.click();
});
assert.deepEqual(log, [0]);
flushSync(() => {
b2.click();
});
assert.deepEqual(log, [0, 'cleanup']);
flushSync(() => {
b1.click();
});
assert.deepEqual(log, [0, 'cleanup']);
}
});

@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];

@ -0,0 +1,13 @@
<script>
import { log } from './log.js';
let x = $state(0);
const cleanup = $effect.root(() => {
log.push(x);
return () => log.push('cleanup');
});
</script>
<button onclick={() => x++}>{x}</button>
<button onclick={cleanup}>cleanup</button>

@ -11,7 +11,7 @@
const nested_cleanup = $effect.root(() => { const nested_cleanup = $effect.root(() => {
return () => { return () => {
log.push('cleanup 2') ; log.push('cleanup 2');
} }
}); });
@ -22,6 +22,6 @@
}); });
</script> </script>
<button on:click={() => x++}>{x}</button> <button onclick={() => x++}>{x}</button>
<button on:click={() => y++}>{y}</button> <button onclick={() => y++}>{y}</button>
<button on:click={() => cleanup()}>cleanup</button> <button onclick={cleanup}>cleanup</button>

Loading…
Cancel
Save