fix: address unowned propagation signal issue (#9510)

* fix: address unowned propagation signal issue

* Add comments
pull/9492/head
Dominic Gannaway 1 year ago committed by GitHub
parent 378093941d
commit 3b2e6eac9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: address unowned propagation signal issue

@ -895,15 +895,19 @@ function mark_signal_consumers(signal, to_status, force_schedule) {
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
const consumer = consumers[i]; const consumer = consumers[i];
const flags = consumer.flags; const flags = consumer.flags;
if ( const unowned = (flags & UNOWNED) !== 0;
(flags & DIRTY) !== 0 || const dirty = (flags & DIRTY) !== 0;
(!runes && consumer === current_effect) || // We skip any effects that are already dirty (but not unowned). Additionally, we also
(!force_schedule && consumer === current_effect) // skip if the consumer is the same as the current effect (except if we're not in runes or we
) { // are in force schedule mode).
if ((dirty && !unowned) || ((!force_schedule || !runes) && consumer === current_effect)) {
continue; continue;
} }
set_signal_status(consumer, to_status); set_signal_status(consumer, to_status);
if ((flags & CLEAN) !== 0) { // If the signal is not clean, then skip over it with the exception of unowned signals that
// are already dirty. Unowned signals might be dirty because they are not captured as part of an
// effect.
if ((flags & CLEAN) !== 0 || (dirty && unowned)) {
if ((consumer.flags & IS_EFFECT) !== 0) { if ((consumer.flags & IS_EFFECT) !== 0) {
schedule_effect(/** @type {import('./types.js').EffectSignal} */ (consumer), false); schedule_effect(/** @type {import('./types.js').EffectSignal} */ (consumer), false);
} else { } else {

@ -0,0 +1,45 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
// The component context class instance gets shared between tests, strangely, causing hydration to fail?
skip_if_hydrate: 'permanent',
async test({ assert, target, component }) {
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.deepEqual(component.log, [0, 'class trigger false', 'local trigger false', 1]);
flushSync(() => {
btn?.click();
});
assert.deepEqual(component.log, [0, 'class trigger false', 'local trigger false', 1, 2]);
flushSync(() => {
btn?.click();
});
assert.deepEqual(component.log, [0, 'class trigger false', 'local trigger false', 1, 2, 3]);
flushSync(() => {
btn?.click();
});
assert.deepEqual(component.log, [
0,
'class trigger false',
'local trigger false',
1,
2,
3,
4,
'class trigger true',
'local trigger true'
]);
}
});

@ -0,0 +1,36 @@
<script context="module">
class SomeLogic {
someValue = $state(0);
isAboveThree = $derived(this.someValue > 3);
trigger() {
this.someValue++;
}
}
const someLogic = new SomeLogic();
</script>
<script>
const {log = []} = $props();
function increment() {
someLogic.trigger();
}
let localDerived = $derived(someLogic.someValue > 3);
$effect(() => {
log.push(someLogic.someValue);
});
$effect(() => {
log.push('class trigger ' + someLogic.isAboveThree)
});
$effect(() => {
log.push('local trigger ' + localDerived)
});
</script>
<button on:click={increment}>
clicks: {someLogic.someValue}
</button>
Loading…
Cancel
Save