Merge branch 'main' into rethink-props

pull/9826/head
Rich Harris 3 years ago
commit 12d6c37e1c

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

@ -409,10 +409,10 @@ export function analyze_component(root, options) {
analysis.reactive_statements = order_reactive_statements(analysis.reactive_statements);
}
// warn on any nonstate declarations that are a) reassigned and mutated and b) referenced in the template
// warn on any nonstate declarations that are a) reassigned and b) referenced in the template
for (const scope of [module.scope, instance.scope]) {
outer: for (const [name, binding] of scope.declarations) {
if (binding.kind === 'normal' && binding.reassigned && binding.mutated) {
if (binding.kind === 'normal' && binding.reassigned) {
for (const { path } of binding.references) {
if (path[0].type !== 'Fragment') continue;
for (let i = 1; i < path.length; i += 1) {

@ -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
};
}

@ -42,7 +42,7 @@ export function readonly(value) {
*/
const readonly_error = (_, prop) => {
throw new Error(
`Props cannot be mutated, unless used with \`bind:\`. Use \`bind:prop-in-question={..}\` to make \`${prop}\` settable. Fallback values can never be mutated.`
`Non-bound props cannot be mutated — use \`bind:<prop>={...}\` to make \`${prop}\` settable. Fallback values can never be mutated.`
);
};

@ -377,10 +377,7 @@ export function class_name(dom, value) {
* @returns {void}
*/
export function text_effect(dom, value) {
render_effect(() => {
const string = value();
text(dom, string);
});
render_effect(() => text(dom, value()));
}
/**

@ -15,5 +15,5 @@ export default test({
},
runtime_error:
'Props cannot be mutated, unless used with `bind:`. Use `bind:prop-in-question={..}` to make `count` settable. Fallback values can never be mutated.'
'Non-bound props cannot be mutated — use `bind:<prop>={...}` to make `count` settable. Fallback values can never be mutated.'
});

@ -15,5 +15,5 @@ export default test({
},
runtime_error:
'Props cannot be mutated, unless used with `bind:`. Use `bind:prop-in-question={..}` to make `count` settable. Fallback values can never be mutated.'
'Non-bound props cannot be mutated — use `bind:<prop>={...}` to make `count` settable. Fallback values can never be mutated.'
});

@ -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