fix: loosen proxy signal creation heuristics (#11109)

* fix: loosen proxy signal creation heuristics

* tighten up test

* update comment

* no need to create a source outside an effect here, because it can't result in a reference

* preserve reference to props.$$events

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/11111/head
Dominic Gannaway 9 months ago committed by GitHub
parent 27891cb2dd
commit 3bb231197e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: loosen proxy signal creation heuristics

@ -19,7 +19,6 @@ import {
import { add_owner, check_ownership, strip_owner } from './dev/ownership.js'; import { add_owner, check_ownership, strip_owner } from './dev/ownership.js';
import { mutable_source, source, set } from './reactivity/sources.js'; import { mutable_source, source, set } from './reactivity/sources.js';
import { STATE_SYMBOL } from './constants.js'; import { STATE_SYMBOL } from './constants.js';
import { updating_derived } from './reactivity/deriveds.js';
import { UNINITIALIZED } from '../../constants.js'; import { UNINITIALIZED } from '../../constants.js';
/** /**
@ -207,13 +206,8 @@ const state_proxy_handler = {
const metadata = target[STATE_SYMBOL]; const metadata = target[STATE_SYMBOL];
let s = metadata.s.get(prop); let s = metadata.s.get(prop);
// if we're reading a property in a reactive context, create a source, // create a source, but only if it's an own property and not a prototype property
// but only if it's an own property and not a prototype property if (s === undefined && (!(prop in target) || get_descriptor(target, prop)?.writable)) {
if (
s === undefined &&
(current_effect !== null || updating_derived) &&
(!(prop in target) || get_descriptor(target, prop)?.writable)
) {
s = (metadata.i ? source : mutable_source)(proxy(target[prop], metadata.i, metadata.o)); s = (metadata.i ? source : mutable_source)(proxy(target[prop], metadata.i, metadata.o));
metadata.s.set(prop, s); metadata.s.set(prop, s);
} }
@ -281,7 +275,7 @@ const state_proxy_handler = {
// we do so otherwise if we read it later, then the write won't be tracked and // we do so otherwise if we read it later, then the write won't be tracked and
// the heuristics of effects will be different vs if we had read the proxied // the heuristics of effects will be different vs if we had read the proxied
// object property before writing to that property. // object property before writing to that property.
if (s === undefined && current_effect !== null) { if (s === undefined) {
// the read creates a signal // the read creates a signal
untrack(() => receiver[prop]); untrack(() => receiver[prop]);
s = metadata.s.get(prop); s = metadata.s.get(prop);

@ -53,7 +53,7 @@ export function asClassComponent(component) {
class Svelte4Component { class Svelte4Component {
/** @type {any} */ /** @type {any} */
#events = {}; #events;
/** @type {Record<string, any>} */ /** @type {Record<string, any>} */
#instance; #instance;
@ -70,7 +70,7 @@ class Svelte4Component {
// Using proxy state here isn't completely mirroring the Svelte 4 behavior, because mutations to a property // Using proxy state here isn't completely mirroring the Svelte 4 behavior, because mutations to a property
// cause fine-grained updates to only the places where that property is used, and not the entire property. // cause fine-grained updates to only the places where that property is used, and not the entire property.
// Reactive statements and actions (the things where this matters) are handling this properly regardless, so it should be fine in practise. // Reactive statements and actions (the things where this matters) are handling this properly regardless, so it should be fine in practise.
const props = proxy({ ...(options.props || {}), $$events: this.#events }, false); const props = proxy({ ...(options.props || {}), $$events: {} }, false);
this.#instance = (options.hydrate ? hydrate : mount)(options.component, { this.#instance = (options.hydrate ? hydrate : mount)(options.component, {
target: options.target, target: options.target,
props, props,
@ -79,6 +79,8 @@ class Svelte4Component {
recover: options.recover recover: options.recover
}); });
this.#events = props.$$events;
for (const key of Object.keys(this.#instance)) { for (const key of Object.keys(this.#instance)) {
if (key === '$set' || key === '$destroy' || key === '$on') continue; if (key === '$set' || key === '$destroy' || key === '$on') continue;
define_property(this, key, { define_property(this, key, {

@ -1,5 +1,5 @@
<script> <script>
const { settings } = $props(); import { settings } from './main.svelte';
</script> </script>
Child: {settings.showInRgb} Child: {settings.showInRgb}

@ -1,19 +1,19 @@
<script context="module"> <script context="module">
export const context = $state({ const context = $state({
settings: { settings: {
showInRgb: true showInRgb: true
} }
}) });
export const settings = context.settings;
</script> </script>
<script> <script>
import Child from './Child.svelte'; import Child from './Child.svelte';
const { settings } = context
</script> </script>
<button onclick={() => settings.showInRgb = !settings.showInRgb}> <button onclick={() => settings.showInRgb = !settings.showInRgb}>
click {settings.showInRgb} click {settings.showInRgb}
</button> </button>
<Child settings={settings} /> <Child />

Loading…
Cancel
Save