Webコンポーネントのプレビューどう作る?
※この記事は Cybozu Frontend Advent Calendar 2023 の19日目の記事です。
早速ですが、以下のようなページを作りたいとします。
- フォーム画面を生成するためのページ(googleフォームのようなイメージ)
- コンポーネントは、UIコンポーネントライブラリを使う
フォーム画面のプレビューのために、機能を持たない(編集ができない)UIの見た目を表示する必要があります。
どんな方法で実装することができるでしょうか?
もし、結論だけ見たいよ〜という方は、末尾のまとめ をご覧ください。
コンポーネントのインターフェースで対応するケース
readonly
まずはreadonly
属性を採用してみます。
<input type="text" value="初期値" readonly />
inputは読み取り専用となり、編集が不可となります。
ただし、UIコンポーネントライブラリが提供するコンポーネントにreadonly
属性をつけるインターフェースがない場合やselect
やcheckbox
などreadonly
属性が使えないタグは、別の方法を検討しなければなりません。
コンポーネントのインターフェースを利用する
用意されているコンポーネントのインターフェースをうまく利用する方法です。
例えば値を変更するロジックがこちらで指定できる場合、値を変更しないように指定します。
具体的には空のコールバックなどを指定すれば良いでしょう。
const [value, setValue] = useState('初期値')
<Input value={value} onChange={() => {}}
ですが、この方法にも落とし穴があります。
値を変更する前にインタラクティブな動作のあるコンポーネントの場合は、onChangeで制御ができません。
MUIのDateRangeCalenarを例に見てみます。
このコンポーネントは「どの月を選択するか」を操作するためのボタンが設置されています。
このボタン自体を非活性にすることは、提供されているインターフェースでは難しいように思います。
機能を持たないUIを別で用意する
次に、「機能を持たないUIを作る」という方法を検討してみましょう。
スタイルのみを持った複製コンポーネントを作る
単純に、UIコンポーネントライブラリの見た目のみを切り出して複製すれば可能です。
この場合、UIコンポーネントライブラリの変更に追従するコストとリスクが伴います。
UIコンポーネントのサイズや色が変わった場合、プロダクションコードがそれに追従していないことに果たして実装者は気付けるでしょうか?
気付いたとしても、毎度スタイルを複製するのはきっとストレスの溜まる作業だと思います。
UIコンポーネントライブラリでスタイル層を提供
UIコンポーネントライブラリ側を変更するという方法もあります。
スタイルをもつコンポーネントと機能を持つコンポーネントを分離して、
それぞれのコンポーネントをexportします。
しかし、このようなエッジケースのために全てのコンポーネントのスタイルをexportしなくてはなりません。UIコンポーネントライブラリは複雑になってしまいます。
社内ライブラリ・OSSライブラリ問わず、果たしてコミュニティはそれを受け入れてくれるでしょうか?
inert
属性という銀の弾丸?
inert
属性をつけたDOMは、「指定したDOM以下のDOMツリー」がブラウザに無視され、まるでブラウザにとって存在しないように振る舞います。
無視、というのは具体的には次のような状態になります。
- フォーカス不可
- 読み上げ不可
- マウスやタッチなどインタラクション系全て不可
読み上げ不可というデメリット
inert
属性をつけたDOMツリーは読み上げに対応しない点に留意する必要があります。
上記のコンポーネントは目視で初期値の値が確認できます。
しかし、読み上げのみを利用しているユーザーはその情報を得ることができません。
altのついていないimgタグのようなもので、これでは情報量に差が出てしまいます。
そのため、表示されている見た目の情報は別のDOMで補完する必要があります。
テストやクローリングでの扱い
ブラウザから完全に無視されるという都合上、inert
属性に対応していないクローリングやテストのツールを使用すると、うまくDOMが取得できない可能性があります。
testing-libraryを使用する場合はhiddenオプションを使うことで、inert
属性のついたDOMツリーのDOMを取得することができます。
getByRole('button', { hidden: true })
まとめ
UIコンポーネントのプレビューを作るには下記のような方法があります。
-
readonly
属性を使用する - コンポーネントのインターフェースを利用する
- ただし上記二つではカバーできないDOMやUIコンポーネントが存在する
- コンポーネントの見た目の層のみを複製する
- 追従のメンテコストとリスクが伴う
- UIコンポーネントライブラリで見た目の層のみをexportする
- ライブラリが複雑になる
-
inert
属性を使用する- 読み上げに懸念がある
- テスト・クローリングに対応できない可能性がある
プロダクションの状況やUIコンポーネントの種類に応じて、どの手段を選ぶか考えると良さそうです。
「こんな案もあるよ!」「こんなリスクもあるんじゃない?」などのクールな意見をお持ちの方はぜひコメントで教えてください🙏
▼Cybozu Frontend Advent Calendar はこちら
Discussion