fix: reuse proxy between objects (#9821)

* chore: reuse proxy between objects

* lint

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/9824/head
Rich Harris 2 years ago committed by GitHub
parent 3fb917dc6d
commit 5797bb34ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: reuse existing proxy when object has multiple references

@ -17,7 +17,7 @@ import {
object_keys
} from '../utils.js';
/** @typedef {{ s: Map<string | symbol, import('../types.js').SourceSignal<any>>; v: import('../types.js').SourceSignal<number>; a: boolean, i: boolean }} Metadata */
/** @typedef {{ s: Map<string | symbol, import('../types.js').SourceSignal<any>>; v: import('../types.js').SourceSignal<number>; a: boolean, i: boolean, p: StateObject }} Metadata */
/** @typedef {Record<string | symbol, any> & { [STATE_SYMBOL]: Metadata }} StateObject */
export const STATE_SYMBOL = Symbol('$state');
@ -35,15 +35,23 @@ const is_frozen = Object.isFrozen;
* @returns {T}
*/
export function proxy(value, immutable = true) {
if (typeof value === 'object' && value != null && !is_frozen(value) && !(STATE_SYMBOL in value)) {
if (typeof value === 'object' && value != null && !is_frozen(value)) {
if (STATE_SYMBOL in value) {
return /** @type {T} */ (value[STATE_SYMBOL].p);
}
const prototype = get_prototype_of(value);
// TODO handle Map and Set as well
if (prototype === object_prototype || prototype === array_prototype) {
define_property(value, STATE_SYMBOL, { value: init(value, immutable), writable: false });
const proxy = new Proxy(value, handler);
define_property(value, STATE_SYMBOL, {
value: init(value, proxy, immutable),
writable: false
});
// @ts-expect-error not sure how to fix this
return new Proxy(value, handler);
return proxy;
}
}
@ -102,15 +110,17 @@ export function unstate(value) {
/**
* @param {StateObject} value
* @param {StateObject} proxy
* @param {boolean} immutable
* @returns {Metadata}
*/
function init(value, immutable) {
function init(value, proxy, immutable) {
return {
s: new Map(),
v: source(0),
a: is_array(value),
i: immutable
i: immutable,
p: proxy
};
}

@ -0,0 +1,30 @@
import { test } from '../../test';
export default test({
html: `
<button>0</button>
<button>0</button>
`,
async test({ assert, target }) {
const [btn1, btn2] = target.querySelectorAll('button');
await btn1?.click();
assert.htmlEqual(
target.innerHTML,
`
<button>1</button>
<button>1</button>
`
);
await btn2?.click();
assert.htmlEqual(
target.innerHTML,
`
<button>2</button>
<button>2</button>
`
);
}
});

@ -0,0 +1,14 @@
<script>
let obj = { count: 0 };
let a = $state(obj);
let b = $state(obj);
</script>
<button onclick={() => a.count += 1}>
{a.count}
</button>
<button onclick={() => b.count += 1}>
{b.count}
</button>
Loading…
Cancel
Save