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 object_keys
} from '../utils.js'; } 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 */ /** @typedef {Record<string | symbol, any> & { [STATE_SYMBOL]: Metadata }} StateObject */
export const STATE_SYMBOL = Symbol('$state'); export const STATE_SYMBOL = Symbol('$state');
@ -35,15 +35,23 @@ const is_frozen = Object.isFrozen;
* @returns {T} * @returns {T}
*/ */
export function proxy(value, immutable = true) { 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); const prototype = get_prototype_of(value);
// TODO handle Map and Set as well // TODO handle Map and Set as well
if (prototype === object_prototype || prototype === array_prototype) { 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 // @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} value
* @param {StateObject} proxy
* @param {boolean} immutable * @param {boolean} immutable
* @returns {Metadata} * @returns {Metadata}
*/ */
function init(value, immutable) { function init(value, proxy, immutable) {
return { return {
s: new Map(), s: new Map(),
v: source(0), v: source(0),
a: is_array(value), 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