-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
The exported names of the reactive Map, Set, and others conflict with platform globals #12192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
100% agree. Runes make reactivity more obvious, the classes however do not |
Why don’t you just rename it when you import it? |
Do you also need to rename the state rune to make it obvious? Its just about understanding reactivity when you quickly ready through the Code. No need to check for an import. The simple let declaration in svelte Files where replaced by runes for this exact reason (and many others) |
The compiler/editor should at least warn when you are updating the non-reactive maps, sets etc. It does that already when you supposedly forgot to declare a variable with $state when you update it somewhere. I have spent hours debugging a complex app, turned out i refactored out functions that use reactive maps to a seperate file that didn't import them. It is far, far too easy to forget this (and have your code fail silently). |
Thanks for the in-depth reply, I understand better now. I think the point made by @hanszoons is the most important implication of this choice. (I later edited it into the OP) But if monkey-patching in dev to warn on usage could catch all silent errors, I was wrong to say code cannot be moved across modules safely. My personal preference would be to reduce ambiguity and avoid the headaches of shadowing globals, even with Some thoughts:
The reactive version is currently assignable to the builtin, but this can be changed by extending the globals with TypeScript, although sometimes you may want the reactive version to be assignable to the builtin, like in helpers: TypeScript REPL with assignment change view TypeScript assignment change example// Pretend this was imported from 'svelte/reactivity':
export class SvelteSet<T> extends Set<T> {
// Existing properties like this make `Set` not assignable to `SvelteSet`:
#sources = new Map();
// Add a conflicting property to make `SvelteSet` not assignable to `Set`.
// @ts-expect-error - this error is silenced because otherwise we couldn't extend the builtin `Set`
readonly [reactive]: true; // comment this out to see the desired errors go away
}
// Extend the global with a conflicting type to make `SvelteSet` not assignable to `Set`:
declare global {
interface Set<T> {
readonly [reactive]: false; // comment this out to see the desired errors go away
}
}
declare const reactive: unique symbol; // one way to "brand" types
export const a = new Set<string>();
export const b = new SvelteSet<string>();
export const b_from_a: SvelteSet<string> = a; // errors as desired without the brand
export const a_from_b: Set<string> = b; // requires the brand to get this error
takes_b(a); // errors as desired without the brand
takes_a(b); // requires the brand to get this error
function takes_a(a: Set<string>): void {}
function takes_b(b: SvelteSet<string>): void {} This assignment change fixes some problems but causes new ones, like functions that don't care if it's reactive would require casting. There may only be downsides if the monkey-patching catches the same kinds of errors. (aside: TypeScript 5.5 reports the unalised |
Uh oh!
There was an error while loading. Please reload this page.
Describe the problem
I like the functionality of
svelte/reactivity
's reactive classes that extend globals likeMap
andSet
, and I appreciate terseness and aesthetics of naming them the same as the globals, but shadowing globals like this is troublesome.Consider that you can't look at code and know if you're dealing with a reactive Svelte class or platform global without looking at imports:
The two kinds of classes differ in behavior and performance. Adding an import changes the semantics of code that otherwise uses normal JS globals, which seems surprising.
Code is therefore not portable across modules with different imports, which can silently cause bugs that tools like TypeScript can't help with. (as @hanszoons describes below)(can probably be mitigated, see replies) This ambiguity feels like the uncanny valley Svelte is trying to avoid as discussed in the feature's main issue.This is editor-specific, but the intelliense isn't obvious. With the global:
When imported from
svelte/reactivity
:The indications this is the reactive one are subtle until you learn the difference.
A workaround to most issues is aliasing yourself. Using
import {Set as ReactiveSet}
:But unless you create your own re-exports of
svelte/reactivity
, you lose autoimports because of the alias. You also lose autoimports in some cases if you don't alias, e.g. pressingctrl+.
on aSet
to import it automatically fromsvelte/reactivity
does not work, although it does include the import in the suggestion dropdown when typing out the global name.To emphasize the point, the Svelte codebase internally prefixes them with
Reactive
presumably because the code uses regular maps and sets often, as is common in JS, and the reactive collections are referenced less. By conflicting with the globals for the convenience of authoring reactive UIs, it's creating fragmentation where some modules and codebases prefer aliasing or not. For un-aliased code, the default, you don't know if a class is reactive without checking the imports,and code cannot be moved around safely without carefully handling imports(can probably be mitigated, see replies).Describe the proposed solution
The easy fix (a breaking change) is removing the export aliases:
Importance
would make my life easier
The text was updated successfully, but these errors were encountered: