Skip to content

Commit 807ffbb

Browse files
authored
fix: only call onchange once for array mutations (#15073)
* only call onchange callbacks once per array mutation * fix * fix
1 parent e42c7cd commit 807ffbb

File tree

2 files changed

+45
-3
lines changed

2 files changed

+45
-3
lines changed

packages/svelte/src/internal/client/proxy.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import {
99
object_prototype
1010
} from '../shared/utils.js';
1111
import { check_ownership, widen_ownership } from './dev/ownership.js';
12-
import { source, set, state } from './reactivity/sources.js';
12+
import { source, set, state, batch_onchange } from './reactivity/sources.js';
1313
import { STATE_SYMBOL, STATE_SYMBOL_METADATA } from './constants.js';
1414
import { UNINITIALIZED } from '../../constants.js';
1515
import * as e from './errors.js';
1616
import { get_stack } from './dev/tracing.js';
1717
import { tracing_mode_flag } from '../flags/index.js';
1818

19+
const array_methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'];
20+
1921
/**
2022
* @template T
2123
* @param {T} value
@@ -168,7 +170,13 @@ export function proxy(value, options, parent = null, prev) {
168170
return v === UNINITIALIZED ? undefined : v;
169171
}
170172

171-
return Reflect.get(target, prop, receiver);
173+
v = Reflect.get(target, prop, receiver);
174+
175+
if (is_proxied_array && array_methods.includes(/** @type {string} */ (prop))) {
176+
return batch_onchange(v);
177+
}
178+
179+
return v;
172180
},
173181

174182
getOwnPropertyDescriptor(target, prop) {

packages/svelte/src/internal/client/reactivity/sources.js

+35-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,32 @@ export function set_inspect_effects(v) {
4545
inspect_effects = v;
4646
}
4747

48+
/** @type {null | Set<() => void>} */
49+
let onchange_batch = null;
50+
51+
/**
52+
* @param {Function} fn
53+
*/
54+
export function batch_onchange(fn) {
55+
// @ts-expect-error
56+
return function (...args) {
57+
let previous_onchange_batch = onchange_batch;
58+
59+
try {
60+
onchange_batch = new Set();
61+
62+
// @ts-expect-error
63+
return fn.apply(this, args);
64+
} finally {
65+
for (const onchange of /** @type {Set<() => void>} */ (onchange_batch)) {
66+
onchange();
67+
}
68+
69+
onchange_batch = previous_onchange_batch;
70+
}
71+
};
72+
}
73+
4874
/**
4975
* @template V
5076
* @param {V} v
@@ -191,7 +217,15 @@ export function internal_set(source, value) {
191217
var old_value = source.v;
192218
source.v = value;
193219
source.wv = increment_write_version();
194-
untrack(() => source.o?.onchange?.());
220+
221+
var onchange = source.o?.onchange;
222+
if (onchange) {
223+
if (onchange_batch) {
224+
onchange_batch.add(onchange);
225+
} else {
226+
onchange();
227+
}
228+
}
195229

196230
if (DEV && tracing_mode_flag) {
197231
source.updated = get_stack('UpdatedAt');

0 commit comments

Comments
 (0)