diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 4acc52c943ec..dcb6f46e8129 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -9,13 +9,15 @@ import { object_prototype } from '../shared/utils.js'; import { check_ownership, widen_ownership } from './dev/ownership.js'; -import { source, set, state } from './reactivity/sources.js'; +import { source, set, state, batch_onchange } from './reactivity/sources.js'; import { STATE_SYMBOL, STATE_SYMBOL_METADATA } from './constants.js'; import { UNINITIALIZED } from '../../constants.js'; import * as e from './errors.js'; import { get_stack } from './dev/tracing.js'; import { tracing_mode_flag } from '../flags/index.js'; +const array_methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort']; + /** * @template T * @param {T} value @@ -168,7 +170,13 @@ export function proxy(value, options, parent = null, prev) { return v === UNINITIALIZED ? undefined : v; } - return Reflect.get(target, prop, receiver); + v = Reflect.get(target, prop, receiver); + + if (is_proxied_array && array_methods.includes(/** @type {string} */ (prop))) { + return batch_onchange(v); + } + + return v; }, getOwnPropertyDescriptor(target, prop) { diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 971bc1b336c4..405a19805015 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -45,6 +45,32 @@ export function set_inspect_effects(v) { inspect_effects = v; } +/** @type {null | Set<() => void>} */ +let onchange_batch = null; + +/** + * @param {Function} fn + */ +export function batch_onchange(fn) { + // @ts-expect-error + return function (...args) { + let previous_onchange_batch = onchange_batch; + + try { + onchange_batch = new Set(); + + // @ts-expect-error + return fn.apply(this, args); + } finally { + for (const onchange of /** @type {Set<() => void>} */ (onchange_batch)) { + onchange(); + } + + onchange_batch = previous_onchange_batch; + } + }; +} + /** * @template V * @param {V} v @@ -191,7 +217,15 @@ export function internal_set(source, value) { var old_value = source.v; source.v = value; source.wv = increment_write_version(); - untrack(() => source.o?.onchange?.()); + + var onchange = source.o?.onchange; + if (onchange) { + if (onchange_batch) { + onchange_batch.add(onchange); + } else { + onchange(); + } + } if (DEV && tracing_mode_flag) { source.updated = get_stack('UpdatedAt');