Webコンポーネントのベストプラクティス
Webコンポーネントを書くのは難しいです。そして良いWebコンポーネントを書くのは本当にもっと難しいです。AstroUXDS Webコンポーネントの構築に最後の1年を費やした後で、私は厳然たる事実を学びました。とってもいいReact/Vue/Angular/FoobarJSのコンポーネントが、とってもいいWebコンポーネントと同じとは限らないということです。Webコンポーネントの世界に初めて足を踏み入れる人たちへ、私が1年前にあったら良かったと思うガイドをここに紹介します。
メモ: これの多くは、デザインシステムを実装する文脈で主に扱われます。
なぜこれをやっているの?
Webコンポーネントの約束と魅力はとても強烈です。あらゆるフレームワークで使うことができる1つのコードベースを書いて維持すること。それはほとんど全員がすぐに心を動かされるものです。しかし、Webコンポーネントは万能薬ではありません。全く新しい規律と考え方の枠組みが必要です。多くの人たちはWebコンポーネントは素晴らしいと言います。例えば「デザインシステムのスタイルで完全にカプセル化されたボタンのコンポーネントが、どれだけ簡単に出荷されるか見てください!」。しかし、彼らが話さないのは、今度はフォームと正しく対話させたり、アクセシビリティを扱う方法をどうやって考え出すかということです。
Webコンポーネントを書くと選んだとき、あらゆる可能性のある使用場面やシナリオをきちんと考え抜くという完全な責任を負うことになります。それと同時に、開発者の経験やユーザーの経験、維持管理性をてんびんにかけながら考えなければいけません。細かいディテールごとに考慮する準備をしておいてください。それができなければ、シャドウDOMは容赦がなく、不機嫌なユーザーになってしまいます。多くの場合、開発者は自分たちで問題を解決する手段を持っていません。
私たちはカスタムの(HTML)エレメントを書いていることを覚えておきましょう。これらの原子は宇宙を作り出すのに十分なほど柔軟でなければなりません。
Webコンポーネントの解剖
良いWebコンポーネントを書くためには、利用可能なAPIのすべてをしっかり理解しないといけません。利用の容易さと拡張性の間で常にジャグリングをしながら、各機能に対してどのAPIを使うのかを慎重に考える必要があります。
もしあなたがフレームワークのマインドセットから来たならば、スロット、プロップス、イベントに既に慣れ親しんでいるかもしれません。Webコンポーネントには、CSSカスタムプロパティとCSSシャドーパーツという2つのユニークなAPIを使うことができます。あなたのコンポーネントのスタイリングは今や独自のAPIです。これをあなたの利益に活かしてください。
スロット
- ✅ 非常に柔軟性がある
- ❌ コンポーネントのコードに複雑さを加える
- ❌ 開発者にもっと多くのボイラープレートを書くことを要求する
スロットはおそらく拡張性に関して最も強力なAPIです。なぜなら、シャドウDOMの外側にあり、任意のカスタムHTMLを含めることができるからです。
プロパティ/属性
- ✅ 使いやすい
- ✅ 利用者にとって馴染みがある
- ❌ 非常に柔軟ではない
プロパティと属性は最も馴染みのある概念です。通常は、状態のようなものを制御するために使用されます。しかし、それらはカスタムコンテンツを許可する際に最も柔軟性に欠けます。
たとえば:
<my-component content="これは私のコンテンツです!"></my-component>
これは基本的な文字列の表示だけで十分な場合には非常にうまく機能します。しかし、もし私が自分のHTMLを渡したかったらどうでしょうか?SVGアイコンを投入したいかもしれませんし、もしくはフォーム全部を入れたければどうでしょうか?全部をただの文字列として入れることはできません。このコンポーネントは私にとってあまり役立ちません。
メソッド
- ✅ ユニックな機能を提供する
- ❌ JavaScriptが必要
公開メソッドは、あなたのコンポーネントが何らかの行動を執る能力がある場合に素晴らしいです。良い例は、おそらくshow()
やhide()
メソッドを持つ典型的なモーダルコンポーネントです。この場合、単にopen
プロップを使用するだけでは、モーダルが開いた_後_に何かをすることを望む開発者にとっては十分ではないかもしれません。なぜなら、まだ使用できないかもしれないからです。代わりに、彼らはモーダルのshow()
メソッドを使用する必要があるかもしれません。これは開き終わった後に解決されるプロミスを返すでしょう。
CSSカスタムプロパティ
- ✅ 柔軟
- ❌ 不注意に使用されると開発者体験(DX)が悪くなる
CSSカスタムプロパティは、開発者がシャドウDOMを突破するための2つの方法の1つです。my-button { background: red; }
をすることは何も影響を与えません。なぜならシャドウDOMのカプセル化のためです。しかし、もし背景色をコントロールするためにCSSカスタムプロパティを使用した場合、開発者は--button-bg-color: red;
のようなことをすることができます。
初期の頃、CSSカスタムプロパティは、Webコンポーネントのスタイルをカスタマイズするために開発者が利用できる唯一の方法でした。これは初期の採用者がめちゃくちゃな量のCSSカスタムプロパティを追加することを導きました。--button-border-radius
、--button-text-color
、--button-font-family
などなど...。想像に可能なCSSプロパティほとんどすべてに対してカスタムプロパティ。それは大混乱でした。 幸いにも、私たちはより良い方法—CSSシャドーパーツを手に入れました。
しかし、CSSカスタムプロパティにはまだ役割があります:
CSS変数は要素にスコープされており、コンポーネント内で再利用することができます。CSS変数の良い例は、内部のすべての要素が共通の幅を共有するのを保証するためにコンポーネント全体で再利用されるかもしれない
--border-width
です。 - Shoelace - CSSカスタムプロパティvs.CSSパーツを使うとき
CSSシャドーパーツ
- ✅ 非常に柔軟
- ❌ 不注意に使用されると維持管理が困難になる
- ❌ 開発者にもっと多くのボイラープレートを書くことを要求する
CSSシャドーパーツは"XYZをどうやってスタイルするか"の問題を解決します。それはあなたがカスタム要素が構成されている"パーツ"を定義することを許可します。あなたの内なるZeldmanにアクセスしてください。シャドーパーツは意味を持つべきです。それらはあなたのコンポーネントの抽象的な一片を代表するべきです。これらはあなたのAPIの一部なので、公開的にどれを露出するかには注意が必要です。
"XYZをどうやってスタイルするか"に対する答えは、"あなたはそうすることはない"ということかもしれません。バックグラウンドカラーが何でもいい色にできることを許可したくないかもしれません。代わりに、あなたが露出するかもしれないプロパティは、いくつかの許可されたオプションを受け入れることしかできないでしょう。
- パーツ名は可能な限りすべてのコンポーネントで一貫しているべきです。
- シャドーパーツは入れ子にできません。
- シャドーパーツは単一の要素しかできません。
my-componet::part(base) > svg { display: none; }
は機能しません。
可能であれば、すべての要素をパーツにしないようにしてください。いったん要素がシャドーパーツになったら、後でマークアップを変更するためには破壊的変更が必要になるでしょう。より詳細な情報については、CSSパーツを作るタイミングをご覧ください。
もしあなたのコンポーネントが十分に小さい(原子レベル)ならば、すべての要素がそれぞれ独自のシャドーパーツを持つことになるかもしれませんが、それで大丈夫です。
正しいツール
ここでとてもシンプルな機能について考えてみましょう—私たちが書かないといけないボタンのコンポーネントが、主要(primary)と副次的(secondary)の2つの異なるバリアントを示す必要があります。この問題をいかに実装するか?
プロップスを使う場合
<my-button type="primary"></my-button>
<my-button type="secondary"></my-button>
メソッドを使う場合
const el = document.querySelector('my-button')
el.setType('primary')
el.setType('secondary')
CSSカスタムプロパティを使う場合
my-button {
--button-background-color: var(--color-primary);
--button-border-color: var(--color-primary);
--button-text-color: var(--color-text);
// + すべてのホバー、アクティブ、フォーカス状態
}
CSSシャドーパーツを使う場合
my-button::part(container) {
background-color: var(--color-primary);
border-color: var(--color-primary);
// などなど
}
これらは与えられた機能を公開する4つの異なる方法です。プロップは明らかに使いやすさの面で勝者です。しかし、もし私たちが2色以上を許可したいと考えたらどうでしょうか?もし私たちがデザインシステムで定義された任意の色を許容したければどうでしょうか?私たちは30以上の別のプロップオプションを追加する必要があるでしょう。
要点は、どのAPIをいつ使用するかに対して最善の答えは存在しないということです。それは、あなたが許容したいものと最高のDXが何であるかを決める問題です。