[svelte5] union型の $state を $derived で参照すると型エラーになることがある
バグだと思って報告もしたのですが結果として svelte のバグではなかったし自分の理解も浅かったので調べてまとめました。
バージョン情報
svelte@5.2.8
typescript@5.6.3
svelte-check@4.1.0
エラーになる例
<script lang="ts">
type Person = { name: string }
let p: Person | undefined = $state(undefined)
$effect(() => {
// load person
setTimeout(() => {
p = { name: "bob" }
}, 1000);
});
let displayName = $derived(p?.name ?? "anon")
</script>
<div>{ displayname }</div>
このコンポーネントに svelte-check で型チェックを実行すると、次のエラーが起きます。
Error: Property 'name' does not exist on type 'never'. (ts)
> let displayName = $derived(p?.name ?? "anon")
修正方法
先に解決方法から書くと、現状としては以下のどちらかの方法で修正できます。解説は下へ。
-
$state
の型引数を明示する- let p: Person | undefined = $state(undefined) + let p = $state<Person | undefined>(undefined)
-
$derived.by
を使う- let displayName = $derived(p?.name ?? "anon") + let displayName = $derived.by(() => p?.name ?? "anon")
解説
型推論の話になるので順番に説明すると以下のようになります。
-
$state
の型はジェネリクス関数として宣言されている [1]declare function $state<T>(initial: T): T;
-
最初のエラーになる例では
$state
の型引数 T が明示されていないため、TypeScript が型推論をおこなう。 -
型推論にて、
initial
の型がundefined
なため 型引数T はundefined
と推論され、それにより戻り値の型もundefined
となる。 -
変数
p
は型注釈があるためPerson | undefined
型だが、代入による narrowing により、そのスコープ内ではundefined
型になる。 [2]// 代入による narrowing の例 let x: number | string = "foo"; // 文字列を代入 console.log(x.length); // narrowing によりこの時点の `x` の型は // `string` 型となり、 `.length` を参照できる x = 3.1; // 数値を代入 x.toFixed(3) // 同様に narrowing により `number` 型となり、 toFixed を呼び出せる
-
undefined?.
のオプショナルチェーンは常に実行されないため、 narrowing によりp
はnever
型になる// narrowing により never 型になる例 const v = true; if (v) { console.log(v); // v: true } else { console.log(v); // v: never }
-
never
はプロパティ参照できないので 型エラーとなる
derived.by について
$derived.by
の場合、与えた関数を呼び出した時点で p
の値が変更されているかどうかは不明なため(言い換えると代入による narrowing の適応範囲外なため) Person | undefined
型として扱われ、p?.
も有効なコードになる
今後について
自分が挙げた issue としてはこれになりまして(英語がクソ雑魚なのはご容赦ください)、「型引数を明示しなくても state を記述できるほうがシンプルじゃない?」みたいなことを言っていますが、それで型定義が必要以上に複雑になるのもどうか、という思いもあります。また $derived
の構文が原因で narrowing が発生するのも微妙なので $derived.by
を使った方がいいと言う感もあり、どうなるかは今後の議論次第という感じがします。ともあれ、現状としては型引数を明示するのがよさそうです。
Discussion