-
Lots of typical Typescript code uses union types. For example, when a value can either be a number, or can be null. ExamplesHere is a very simplistic example let count = null as number | null;
const increment = () => { count = (count ?? 0) + 1; };
if(count != null) {
// notice how Typescript is happy, because it knows that count is not null
console.log("next value" + (count + 1));
} Attempting to do the same using a signal and the export default function Counter() {
const [count, setCount] = createSignal<number|null>(null);
return (
<button class="increment" onClick={() => setCount((count() ?? 0) + 1)}>
Clicks: {count()}
<Show when={count() != null}>
{/* Typescript complains here, because it cannot figure out that count() won't be null */}
next value will be {count() + 1}
</Show>
</button>
);
} This is mostly a toy example to illustrate my point. (The documentation also uses that toy example https://www.solidjs.com/guides/typescript#control-flow-narrowing ) This one can be worked around, but I think it becomes very painful once one writes anything bigger. Why?I feel like this is a Typescript limitation, made worse by SolidJS's signal API design choice. As in, when using props instead of signals, it might become possible to use type assertions to properly type the But, this is much harder with a signal. A signal has a getter, which simply a function. However, a function does not have to return the same value every time, which means that Typescript cannot narrow its return type. e.g. const [count, setCount] = createSignal<number|null>(null);
if(count() != null) {
console.log("next value" + (count() + 1));
} The workaround there is to introduce a local variable, but of course that doesn't work in JSX. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 14 replies
-
Regarding the Switch and Match, here's a simple example that doesn't appear to have a good workaround. type User =
| { tag: "anonymous" }
| { tag: "user"; name: string }
| { tag: "admin"; email: string };
const ViewA = (props: User) => (
<Switch>
<Match when={props.tag === "anonymous"}>Hello stranger!</Match>
<Match when={props.tag === "user"}>
{/* Typescript errors here */}
Hello {props.name}
</Match>
<Match when={props.tag === "admin"}>
{/* and another Typescript error here */}
Hello admin at {props.email}
</Match>
</Switch>
); |
Beta Was this translation helpful? Give feedback.
-
You can help yourself with a guard function: const isDef = <T extends any>(accessor: Accessor<T | undefined>): accessor is Accessor<T> =>
accessor() !== undefined;
if (isDef(count)) {
// herein, count will be handled as () => number
} |
Beta Was this translation helpful? Give feedback.
-
Hi, this is not an answer, but I guess another way in which typescript + preact + signals cause a bit of issue: In the code below, the object "arg" is a "dict" (sorry, a python-ism for just a regular object with keys and values (not a JS Map!) that contains all of the global signals for my app. I then try to have a function that gives a snapshot of the app state for rendering "past history" and this is where I ran into apparently unsolvable issue with typescript.
I tried all kinds of ways to get rid of that error. After about 5 hours, I gave up. |
Beta Was this translation helpful? Give feedback.
I tried to bring up my concern around idepotent functions with TS people I knew and they basically told me it was highly unlikely they'd ever be able to implement that in a reasonable way. I brought up how
getter
s are basically broken the opposite way and they basically consider it a choose your battles thing. To be fair I'd still write an issue but I lack the TS knowledge to even write up what I want it to do. But if this can be fixed the rest can be improved.Having TS change API is hard... Mostly that there are reasons for the API the way it is. Reasons why for composition I don't just disfavor
. value
because of some opinion. We've talked about this at length before. I have a whol…