ドワンゴでニコニコ生放送のWebフロントエンジニアをやっています、 @misuken です。
最近、嬉しいことに、コンポーネント分類の決定版とも言える BCD Design の魅力に気付いてくださる方が増えてきました。
BCD Design の世界はさらに分析が進んでいるので、またさらに有益な情報をお届けしたいと考えています。
今回はリスト系のコンポーネントを作る際に有用な知見を紹介します。
はじめに
React 特に TypeScript と組み合わせてフロントで ViewComponent(VC) を作成していると、カードの一覧や画像の一覧など、リストを表現するコンポーネントを作る機会があります。
リストと言えば配列を使うわけですが、配列の名前を次のどっちにしようか迷った経験はないでしょうか?
方式 | card | image |
---|---|---|
複数形 | cards | images |
List 式 | cardList | imageList |
一度決めたらアプリケーション単位でどちらかに統一しないと使い勝手の悪いシステムになってしまうため、わりと重要な判断だったりします。
多くの人は短く書けるし、 images.map(image => 〜)
と書くとスマートな感じがするので、複数形を選んでいるかもしれません。
自身も VC を書き始めた当初、どちらにしようかなと慎重に選んだ記憶があります。
しかし、VC の世界では HTML と親和性のある List を付加する方式と、とても相性が良いことがわかっているので、複数形の問題点と List 式が優位な理由ついて説明します。
名称的な理由
名前の単純な法則性
英語における複数形のほとんどは末尾に "s" を付ければよいものの、単純に全ての末尾に "s" を付ければよいというものではありません。
画像 | ボックス | 履歴 | 法則性 |
---|---|---|---|
Image | Box | History | 対象 |
Images | Boxes | Histories | 対象 + ( s or es ) or ( - y + ies ) |
ImageList | BoxList | HistoryList | 対象 + List |
単語の末尾によっては "s" の付け方が変化するため、英語が苦手な人だとうっかり間違える可能性があります。
List 式は後ろに追加するだけなので、何も心配がありません。
HTML タグ名との親和性
VC は最終的に HTML を出力することが目的になります。
その場合、リスト系のものは <ul>
や <ol>
に加えて <li>
を使用します。
コンポーネント名 | 表現するHTMLタグ | 正式名 | 結果 |
---|---|---|---|
Image | img | image | image を image で表現 |
Images | ul | unordered list | image array を list で表現 |
ImageList | ul | unordered list | image list を list で表現 |
Images もほぼ的を得ているものの、 ImageList はそのままです。
ちなみに、 <li>
タグはリストタグと間違われることもありますが、正確には list item であることに気を付けましょう。
タグ名 | 正式名 | 意味 |
---|---|---|
ol | ordered list | 序列付きリスト |
ul | unordered list | 序列なしリスト |
li | list item | リストの項目 |
構造的な理由
複数形の意味がブレる
次の表は一行だけイレギュラーになっている場所があります。
コンポーネント名 | Props | コンポーネント |
---|---|---|
Image | オブジェクト | オブジェクト(関数 or クラス) |
Images | オブジェクト配列 | オブジェクト(関数 or クラス) |
ImageList | オブジェクト | オブジェクト(関数 or クラス) |
Images 形式の場合、Props の実態である images という変数は配列になります。
しかし、コンポーネントとしての Images は複数形の名前ではあるものの配列ではありません。
一方 ImageList は単体の List なので、 Props の実態としても、コンポーネントとしてもオブジェクトのままで、名前と型の関係性が一致しています。
滅多に無いとは思いますが、コンポーネントの配列が必要になったら Images は二次元配列のときだけ List を使って ImagesList にするルールを作らざるを得なくなるでしょう。
または ImageTable のほうがマシと思うかもしれませんが、HTML には table もあるので、 ImageTable が画像をテーブルで表現するコンポーネントを指すのか、 Images コンポーネントの配列を指すのかわからなくなってしまいます。1
一方 ImageList のほうは List コンポーネントの配列なので ImageLists とするだけで済みます。
構造が一致しない
ここでは決定的な差が見えてきます。
コンポーネント名 | 型 | 型定義 | 出力したいHTML |
---|---|---|---|
Image | ImageProps | { src: string } |
<image /> |
Images | ImageProps[] | { src: string }[] |
<ul><li><image /></li></ul> |
ImageList | ImageListProps ListProps<ImageProps> |
{ items: { src: string }[] } |
<ul><li><image /></li></ul> |
Images の場合、Props の配列が対応しているのは事実上 <li>
であり、 <ul>
は暗黙的に追加されるものになっています。
実は配列で扱っているものはリスト本体ではなく項目の配列だったわけです。
<ul>
に対して何らかの情報を渡したいと思っても、渡す術がありません。
これは情報量が不足した構造的であることを指しています。
Images が項目の一覧でしかないということは、本質的にはリストを意味する <ul><li><image /></li></ul>
ではなく、項目を意味する <li><image /></li>
と対応していることになります。
List 式の優位性
では ImageList のほうはどうでしょう。
ImageList は Props が <ul>
に items の要素が一つの <li>
に対応しています。
最初の方で説明したように、 <ul>
は unordered list で <li>
は list item です。
ImageList が list に items が item へマッピングされる形はまさに理想的と言えます。
実施のコードでも imageList.items.map(item => 〜)
のように、実装と意味が合致します。
実際に完全に構造と噛み合うようにするには Props を次のような構造にするだけです。
interface Props {
items: Array<{
image: { src: string }
}>
}
これでとても自然且つ、簡潔なコードになります。
const Component: React.FunctionComponent<ImageListProps> = ({ items, ...props }) => {
return (
<ul {...props}>
{items.map(({ image, ...item}) => <li {...item}><Image {...image} /></li> )}
</ul>
);
}
BCD Design の観点から
BCD Design ではコンポーネント名に単語の複数形を使用することはありません。
Base となる型(UI)の名前が重要な役割を果たすためです。
今回の例で言えば List がそれに該当します。
素のリストが List 型で、 ImageList を作るなら Base + Base の基礎的なコンポーネントとなります。
型が付くことでそのコンポーネントが何であるかが認識しやすくなり、全体の安定した成長につながります。
まとめ
いかがだったでしょうか。
今回はリスト系のコンポーネントに対して、複数形と List 式、それぞれの名前を使ったときにどのようなことが起きるのかを紹介しました。
複数形は項目配列と HTML のリストの構造が一致せず、様々な歪に直面するのに対し、 List 式は構造的な一致度が高くて法則的で安定した実装に繋がることがわかりました。
実際の開発ではこれ以外にも様々な分野で細かい見極めを行い、それらの積み重ねによって大規模でもスマートで簡潔に法則性のある世界を実現しています。
迷いなく、気持ち良くコードを書いていきたいですね。
-
本来のスコープをはみ出ると、その歪が永久に連鎖していきます。設計は減点法なので後から挽回することは出来ず、悪化をどこまでで食い止められるかでしかありません。 ↩