Skip to content

Commit 5797bb3

Browse files
authored
fix: reuse proxy between objects (#9821)
* chore: reuse proxy between objects * lint --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 3fb917d commit 5797bb3

File tree

4 files changed

+65
-6
lines changed

4 files changed

+65
-6
lines changed

.changeset/smart-parents-swim.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: reuse existing proxy when object has multiple references

packages/svelte/src/internal/client/proxy/proxy.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
object_keys
1818
} from '../utils.js';
1919

20-
/** @typedef {{ s: Map<string | symbol, import('../types.js').SourceSignal<any>>; v: import('../types.js').SourceSignal<number>; a: boolean, i: boolean }} Metadata */
20+
/** @typedef {{ s: Map<string | symbol, import('../types.js').SourceSignal<any>>; v: import('../types.js').SourceSignal<number>; a: boolean, i: boolean, p: StateObject }} Metadata */
2121
/** @typedef {Record<string | symbol, any> & { [STATE_SYMBOL]: Metadata }} StateObject */
2222

2323
export const STATE_SYMBOL = Symbol('$state');
@@ -35,15 +35,23 @@ const is_frozen = Object.isFrozen;
3535
* @returns {T}
3636
*/
3737
export function proxy(value, immutable = true) {
38-
if (typeof value === 'object' && value != null && !is_frozen(value) && !(STATE_SYMBOL in value)) {
38+
if (typeof value === 'object' && value != null && !is_frozen(value)) {
39+
if (STATE_SYMBOL in value) {
40+
return /** @type {T} */ (value[STATE_SYMBOL].p);
41+
}
42+
3943
const prototype = get_prototype_of(value);
4044

4145
// TODO handle Map and Set as well
4246
if (prototype === object_prototype || prototype === array_prototype) {
43-
define_property(value, STATE_SYMBOL, { value: init(value, immutable), writable: false });
47+
const proxy = new Proxy(value, handler);
48+
define_property(value, STATE_SYMBOL, {
49+
value: init(value, proxy, immutable),
50+
writable: false
51+
});
4452

4553
// @ts-expect-error not sure how to fix this
46-
return new Proxy(value, handler);
54+
return proxy;
4755
}
4856
}
4957

@@ -102,15 +110,17 @@ export function unstate(value) {
102110

103111
/**
104112
* @param {StateObject} value
113+
* @param {StateObject} proxy
105114
* @param {boolean} immutable
106115
* @returns {Metadata}
107116
*/
108-
function init(value, immutable) {
117+
function init(value, proxy, immutable) {
109118
return {
110119
s: new Map(),
111120
v: source(0),
112121
a: is_array(value),
113-
i: immutable
122+
i: immutable,
123+
p: proxy
114124
};
115125
}
116126

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
html: `
5+
<button>0</button>
6+
<button>0</button>
7+
`,
8+
9+
async test({ assert, target }) {
10+
const [btn1, btn2] = target.querySelectorAll('button');
11+
12+
await btn1?.click();
13+
assert.htmlEqual(
14+
target.innerHTML,
15+
`
16+
<button>1</button>
17+
<button>1</button>
18+
`
19+
);
20+
21+
await btn2?.click();
22+
assert.htmlEqual(
23+
target.innerHTML,
24+
`
25+
<button>2</button>
26+
<button>2</button>
27+
`
28+
);
29+
}
30+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script>
2+
let obj = { count: 0 };
3+
4+
let a = $state(obj);
5+
let b = $state(obj);
6+
</script>
7+
8+
<button onclick={() => a.count += 1}>
9+
{a.count}
10+
</button>
11+
12+
<button onclick={() => b.count += 1}>
13+
{b.count}
14+
</button>

0 commit comments

Comments
 (0)