Hacker News new | ask | show | jobs
by rich_harris 995 days ago
We've toyed with this idea. There's a couple of problems though. Firstly, if you have this...

type Reactive<T> = T; function $state<T>(value: T): Reactive<T>

...then TypeScript will 'unwrap' the type anyway, unless you do funky stuff like this...

type Reactive<T> = T & { [uniquesymbol]: any };

...in which case things like `value += 1` will cause type errors because it coercies `value` from `Reactive<number>` to `number`.

But it also creates problems here:

let message = $state('hello'); obj = { message };

The type of `obj.message` is Reactive<string>, but it's _not_ reactive — it's a non-reactive snapshot of the value when the object was created.

It's possible that we can do some fun stuff with TypeScript plugins, but we haven't dived too deeply into it yet.

2 comments

> TypeScript will 'unwrap' the type anyway, unless you do funky stuff like this […] in which case things like `value += 1` will cause type errors because it coercies `value` from `Reactive<number>` to `number`.

Yeah. I’ve spent more time than I’d like to admit trying to find a nice solution to this, and I’ve ultimately arrived at “it needs type system support to work for the general case”. I think you can make your brand (symbol) optional to address this case, but it doesn’t address this:

> The type of `obj.message` is Reactive<string>, but it's _not_ reactive — it's a non-reactive snapshot of the value when the object was created.

And here is the real problem: the types are right (in this case)! It’s Svelte that is wrong (in that it deviates from the JavaScript language semantics, which TypeScript has correctly modeled).

This leads to my other ultimate conclusion with nominal primitive types: most of the time you’re better off just boxing the value… unless, or until, it becomes problematic for other reasons (eg performance, which would probably be a concern here, albeit one worth testing). Boxing allows correct, refined typing; it makes semantics of the value clear. The only downside is convenience… but this sort of convenience is exactly the kind of thing people rightly judge about the JS ecosystem.

I’m not likely to use Svelte (for a variety of other reasons), so I don’t have a particular dog in this race… but I will say I find more explicit types for signals a great deal nicer to work with. Whether the explicit mechanism is a function call (like Solid) or an accessor (like many others).

With Rust, mutable variables are underlined by RustAnalyzer by default - it would be really useful for reactive variables in Svelte to have a similar distinction.