fix: batch assignment to length of an array

pull/15579/head
paoloricciuti 6 months ago
parent 3e886c71f0
commit 25e03b3fad

@ -19,6 +19,15 @@ import { active_effect, get } from './runtime.js';
const array_methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort']; const array_methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'];
/**
* Used to prevent batching in case we are not setting the length of an array
* @param {any} fn
* @returns
*/
function identity(fn) {
return fn;
}
/** /**
* @param {ValueOptions | undefined} options * @param {ValueOptions | undefined} options
* @returns {ValueOptions | undefined} * @returns {ValueOptions | undefined}
@ -306,12 +315,20 @@ export function proxy(value, _options, parent = null, prev) {
var s = sources.get(prop); var s = sources.get(prop);
var has = prop in target; var has = prop in target;
// if we are changing the length of the array we batch all the changes
// to the sources and the original value by calling batch_onchange and immediately
// invoking it...otherwise we just invoke an identity function
(is_proxied_array && prop === 'length' ? batch_onchange : identity)(() => {
// variable.length = value -> clear all signals with index >= value // variable.length = value -> clear all signals with index >= value
if (is_proxied_array && prop === 'length') { if (is_proxied_array && prop === 'length') {
for (var i = value; i < /** @type {Source<number>} */ (s).v; i += 1) { for (var i = value; i < /** @type {Source<number>} */ (s).v; i += 1) {
var other_s = sources.get(i + ''); var other_s = sources.get(i + '');
if (other_s !== undefined) { if (other_s !== undefined) {
if (typeof other_s.v === 'object' && other_s.v !== null && STATE_SYMBOL in other_s.v) { if (
typeof other_s.v === 'object' &&
other_s.v !== null &&
STATE_SYMBOL in other_s.v
) {
other_s.v[PROXY_ONCHANGE_SYMBOL](options?.onchange, true); other_s.v[PROXY_ONCHANGE_SYMBOL](options?.onchange, true);
} }
set(other_s, UNINITIALIZED); set(other_s, UNINITIALIZED);
@ -345,7 +362,7 @@ export function proxy(value, _options, parent = null, prev) {
} }
set(s, proxy(value, clone_options(options), metadata)); set(s, proxy(value, clone_options(options), metadata));
} }
})();
if (DEV) { if (DEV) {
/** @type {ProxyMetadata | undefined} */ /** @type {ProxyMetadata | undefined} */
var prop_metadata = value?.[STATE_SYMBOL_METADATA]; var prop_metadata = value?.[STATE_SYMBOL_METADATA];

@ -0,0 +1,18 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
const [btn, btn2] = target.querySelectorAll('button');
flushSync(() => {
btn.click();
});
assert.deepEqual(logs, [[{}, {}, {}, {}, {}, {}, {}, {}]]);
flushSync(() => {
btn2.click();
});
assert.deepEqual(logs, [[{}, {}, {}, {}, {}, {}, {}, {}], []]);
}
});

@ -0,0 +1,14 @@
<script>
let array = $state([], {
onchange() {
console.log($state.snapshot(array));
}
});
</script>
<!-- clicking either of these buttons should result in at most one log -->
<button onclick={() => array = [{}, {}, {}, {}, {}, {}, {}, {}]}>populate array</button>
<button onclick={() => array.length = 0}>clear array</button>
<!-- without this, nested proxies aren't created -->
<pre>{JSON.stringify(array)}</pre>
Loading…
Cancel
Save