sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
80.21k stars 4.27k forks source link

svelte stores are greedy and have no equivalent of <svelte:options immutable={true} /> #10453

Open WHenderson opened 9 months ago

WHenderson commented 9 months ago

Describe the problem

Currently, sveltes stores use a greedy comparator to determine if a value has changed:

/**
 * @param {any} a
 * @param {any} b
 * @returns {boolean}
 */
export function safe_not_equal(a, b) {
    // eslint-disable-next-line eqeqeq
    return a != a
        ? // eslint-disable-next-line eqeqeq
            b == b
        : a !== b || (a && typeof a === 'object') || typeof a === 'function';
}

This makes sense as a default as it matches the nature of reactive blocks in svelte 4.

Svelte 4 also supports immutable reactivity (). see: example

It would be useful if the stores had a similar parallel, allowing the ability to specify an alternative equality checker such as a strict equality checker.

Describe the proposed solution

Update the store api/implementation to allow a configurable comparison function which would override the default safe_not_equal.

e.g.

/**
 * @param {any} a
 * @param {any} b
 * @returns {boolean}
 */
export function strict_not_equal(a, b) {
    return a !== a;
}

possible apis:

extend the current implementation

const w1 = writable({ value: 1 }, { comparator: strict_not_equal });
const w2 = writable({ value: 1 }, () => {}, { comparator: strict_not_equal })
const r1 = readable({ value: 1 }, { comparator: strict_not_equal })
const r2 = readable({ value: 1 }, () => {}, { comparator: strict_not_equal })
const d1 = derived(w1, w1 => {}, undefined, { comparator: strict_not_equal })
const d2 = derived([w1,w2], ([w1, w2]) => {}, undefined, { comparator: strict_not_equal })

offer an alternative import

import { readable, writable, derived } from 'svelte/immutable-stores';

Importance

nice to have

rmunn commented 9 months ago

This was the root cause of a bug I spent a while tracking down in one of my projects, where I was firing off an API request when a derived store updated, and I was seeing it fire off twice in the space of a few milliseconds. The API request happened to have a bug that made it non-idempotent, but it took me a while to realize that when the store's value updated to an object with identical values as the previous value, the derived store was triggering an update.