概要
今後の変更への対応を楽にするため、基本的にはsvelte:xxx
系のコンポーネントはラップしたものを使うようにする方針です。
svelte:elementも同様に行いたいのと、Markuplintのas
の恩恵を受けたいのでsvelte:elementのラッパーを作ります。
svelte:elementのラッパー
インターフェースとしてはas
でHTML要素名(aとかbuttonとか)を受け取り、
as
で指定したHTML要素が受け取ることのできるprops
だけを追加で受け取れるようにしたいです。
HTML要素名
keyof HTMLElementTagNameMap
で取得できます。
これの存在を知りませんでした。そんなんあったんかい。
いけそうに思えるNG例
<script lang="ts" generics="P extends SvelteHTMLElements[T], T extends keyof HTMLElementTagNameMap">
import type { SvelteHTMLElements } from 'svelte/elements';
const {
as,
children,
...elementProps
}: {
as: T;
} & P = $props();
</script>
<svelte:element this={as} {...elementProps}>
{@render children?.()}
</svelte:element>
いけそうな雰囲気を感じますが、
これだと{...elementProps}
の部分でUnion型複雑すぎて無理ィ!というエラーが出ます。
Expression produces a union type that is too complex to represent.ts(2590)
正解
<script lang="ts" generics="P extends SvelteHTMLElements[T], T extends keyof HTMLElementTagNameMap">
import type { SvelteHTMLElements } from 'svelte/elements';
type OmitOldOnProps<T> = {
[P in keyof T as P extends `on:${string}` ? never : P]: T[P];
};
const {
as,
children,
...elementProps
}: {
as: T;
} & OmitOldOnProps<P> = $props();
</script>
<svelte:element this={as} {...elementProps}>
{@render children?.()}
</svelte:element>
on:
を取り除くとうまくいきます。on:
周りのSvelte4の型が残っているとその分型周りがややこしくなるのでしょうか…
これにて一件落着!
Tips: SvelteでGenericsを使うとき使っていた謎のテクニック(今は気にしなくてOK)
<script lang="ts" generics="P extends SvelteHTMLElements[T], T extends keyof HTMLElementTagNameMap">
import type { SvelteHTMLElements } from 'svelte/elements';
</script>
古いバージョンのSvelteだとこれがSvelteHTMLElementsなんぞ知らん!というlintエラーになっていました。
SvelteHTMLElementsがgenericsよりも後に書かれているのが原因みたいなので、script moduleを悪用(?)すると回避できます。
<script lang="ts" module>
import type { SvelteHTMLElements } from 'svelte/elements';
</script>
<script lang="ts" generics="P extends SvelteHTMLElements[T], T extends keyof HTMLElementTagNameMap">
</script>
古いバージョンのSvelteを使っている人は覚えておいて損はないテクニックです。
参考
こちらを試してみたところ解決しました。ありがとう…🙏