💲

SvelteでatomicなHTMLコンポーネントを宣言する

2023/12/15に公開

やりたいこと

各HTMLタグのみを返す最小単位のuiコンポーネントを作りたい。

Reactで書くと以下のようなもの。

import type { ButtonHTMLAttributes } from "react";

export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;

export function Button({ ...props }: ButtonProps) {
  return (
    <button
      className={`${props.className} h-10 w-36 rounded-full`}
      {...props}
    />
  );
}

HTMLタグにデフォルトで備わっているattributes(属性)をpropsとして受け取り、最低限のスタイルだけ当てている。

(上のコンポーネントはtailwindcssでスタイルを当てています。)

結論

以下パッケージをインストール

https://github.com/baseballyama/svelte-preprocess-delegate-events

以下のように指定するとpropsとイベントを親から受け取れる。

<script lang="ts">
  import type { HTMLButtonAttributes } from "svelte/elements";
  type $$Props = HTMLButtonAttributes;
</script>

<button
  class={`${$$props.class} h-10 w-36 rounded-full`}
  on:*
  {...$$props}
>
  <slot />
</button>

解説

attributesを受け渡し

$$propsを使用するとexport letで宣言していないpropsを受け取れる。

<button {...$$props} />

このままだと<button>に存在しない属性も渡せてしまうので、$$Propsの型を上書きしておく。

type $$Props = HTMLButtonAttributes;

event forwarding

$$propsだけだと<Button on:click={...} />などイベントが実行されなくなってしまう。

eventを親から指定できるようにする方法はsvelte公式ドキュメントの以下に記載されている。
https://svelte.jp/docs/basic-markup#attributes-and-props

clickイベントを親から指定したい場合は以下のようにすればいい。

<button on:click />

全てのイベントを記述するのはめんどくさいのでうまいこと指定したい。

svelte公式では v5で実装予定。
https://svelte-5-preview.vercel.app/docs/event-handlers#bubbling-events

svelte v3 ~ v4の場合は以下のサードパーティライブラリを使用
https://github.com/baseballyama/svelte-preprocess-delegate-events

インストール手順はREADMEにありますが、一応紹介。

pnpm add -D svelte-preprocess-delegate-events

プロジェクトルートにsvelte-jsx.d.tsを作成して以下内容をペースト

declare namespace svelteHTML {
  type HTMLProps<Property extends string, Override> =
    Omit<
      Omit<import('svelte/elements').SvelteHTMLElements[Property], keyof EventsWithColon<Omit<svelte.JSX.IntrinsicElements[Property & string], svelte.JSX.AttributeNames>>> & EventsWithColon<Omit<svelte.JSX.IntrinsicElements[Property & string], svelte.JSX.AttributeNames>>,
      keyof Override
    > & Override & (Record<'on:*', (event: Event & { currentTarget: EventTarget & EventTarget }) => any | never> | object);
}

tsconfig.jsonに以下を追加

{
  "include": ["./src/**/*", "./svelte-jsx.d.ts"]
}

これでon:*とすることで全てのイベントを親から指定できます。

注意

svelteのコンパイル性能を最大限引き出すには$$propsを使用せずに必要なpropsのみexportする方がいいみたいです。

$$propsは、exportで宣言されていないものも含めて、コンポーネントに渡されるすべてのプロパティ (props)を参照します。
$$propsを使用すると、特定のプロパティを参照するときよりもパフォーマンスが低下します。なぜなら、どのプロパティを変更した場合でも、Svelte は$$propsのすべての使用を再チェックするからです。
しかし、例えばコンパイル時にどのようなプロパティがコンポーネントに渡されるかわからない場合などには便利です。

https://svelte.jp/docs/basic-markup#attributes-and-props

svelteは学習中ですので、間違いや他にいい方法があればご教授下さい。

Discussion