🌟

Svelteの$$Genericをつかってみよう

2023/03/14に公開

注意

2023/07/19

この記事は古くなりました。
どうやらこの構文は不採用になり、
代わりにこちらの構文が採用になるようです。
https://github.com/dummdidumm/rfcs/blob/ts-typedefs-within-svelte-components/text/ts-typing-props-slots-events.md#solution

やりたかったこと

  • プラスアイコンだけが見えているボタンコンポーネントを作りたい
  • aria-labelで何をするボタンなのかを読み上げさせたい
  • aria-labelに空の文字を入れたくない(意味がないので)
    • aria-labelが空になってしまう場合そもそもボタン自体表示しないようにしてミスに気付けるようにしたい

この3点です。

そもそも

requiredProp: stringなpropsって空文字を受け付けたくないですよね。
requiredとは…?

基本的に空文字を入れることはないのでいままで気にしたことはなかったのですが、よく考えるとなにもrequiredにできていないことに気づいたので空文字を許可しない型探しに出ました。

StackOverflowに同じような質問が投げられており、どうやら関数だといい感じに書けるということがわかりました。

Reactは素のJSに近い形で書けるので関数コンポーネントでこれを書くのは割と簡単で、上記記事にも答えが書いてあります。

じゃあSvelteでやるとどうなるのかというと以下です。
Point!!部分がポイントです。

js部分

//<script lang="ts"> // svelteのハイライトが効かないのでコメントアウト
	import Ico from '$lib/components/svg/Ico.svelte' // アイコンを表示
	type T = $$Generic<string> // Point!!①
	export let notEmptyLabel: T extends '' ? never : T // Point!!②
	$: label = notEmptyLabel.trim()
//</script>

Point①

まず、SvelteでGenericsを使う場合は$$Genericを使います。

これなのですが、探した感じドキュメントには載っていなかったような…
ホバーしても何にも表示されないのでなんもわからん…になりがちな気がします。

僕は<string>できることに気づかなかったので なんとなくできないものだと思い込んでいたのですが、YENDさんからのリプライでふと気づきを得て試しに書いてみたところどうやら型変数を渡せるようです!(ありがとうございました!)

Point②

ここまでくればあとは普通のTypeScriptです。
T extends '' ? never : Tの部分で型を分岐してあげれば空文字とサヨナラできます。

これで必須プロパティが空で入ってくるのは型で防げましたが、半角スペースを入れたりしてくる輩がいるかもしれないので一応
notEmptyLabel.trim()しています。

もっとちゃんとやりたい人は頑張ってください。

html部分

{#if !!label}
	<button
		type="button"
		aria-label={label}
		on:click
		class="grid place-items-center w-12 h-12 bg-white"
	>
		<span class="block w-6">
			<Ico name="add" />
		</span>
	</button>
{/if}

{#if !!label}で分岐させています。

まとめ

こんな感じで、Svelteでは$$Genericを使うとジェネリクスを使用することができます。
応用するとセレクトボックスコンポーネントに配列を渡して値にセットできるのはその中のものだけ とかもできますね。

Svelte、結構いいと思うんですが記事が少ないのでとっつきにくさがあるかもしれません。
簡単なものからよくある実装の例など、いろんな記事が読みたいですね!

Discussion