フロントのコンポーネント設計の学び方を分かりやすく説明する
概要
React.jsでのコンポーネント設計について、チュートリアル内の下記のリストを例に考えたい。
初めてコンポーネントを作成した際は、コンポーネントのベストプラクティスを理解せずに実装してしまっていた。
今回は可読性と再利用性についてリファクタリングしつつ説明していく。
const listItems = people.map(person =>
<li key={person.id}>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
known for {person.accomplishment}
</p>
</li>
);
対象読者
- React.jsのチュートリアル終わったぐらいの人
- コンポーネント設計を学んでいる人
いいね!してね
この記事の事例は必要に応じて今後追記していく予定です!
「新しい事例が知りたい」「他の事例も知りたい」と思った人は、ぜひこの記事にいいね👍してください。筆者のモチベーションにつながります!
それでは以下が本編です。
結論
import { people as chemists } from "../../data.js";
import ScientistProfileList from "./scientistProfileList.js";
import ScientistProfileListItem from "./scientistProfileListItem.js";
import ScientistProfileListItemIcon from "./scientistProfileListItemIcon.js";
import ScientistProfileListItemSummary from "./scientistProfileListItemSummary.js";
export default function ScientistsIndex() {
return (
<ScientistProfileList>
{chemists.map((chemist) => (
<ScientistProfileListItem key={chemist.id}>
<ScientistProfileListItemIcon
imageId={chemist.imageId}
alt={chemist.name}
/>
<ScientistProfileListItemSummary chemist={chemist} />
</ScientistProfileListItem>
))}
</ScientistProfileList>
);
}
説明すること
- 可読性が高いコンポーネントとは?
- 適切なコンポーネントの粒度とは?
- コンポーネントの作成
- コンポーネント名の命名規則
- 見づらい例
- コンポーネントの再利用性について検討する
可読性が高いコンポーネントとは?
可読性が高いとはどういうことか?
他の人や自分自身でも理解しやすく、修正や保守が容易であることを指す。
具体的には
✅ 明確性(Clarity): コードは明確で、意図が明確に表現されている。変数や関数の命名がわかりやすく、コードの目的や機能が推測しやすい。
✅ シンプルさ(Simplicity): コードはシンプルで、不要な複雑さが排除されている。不必要な条件分岐やループのネストが少なく、処理が直感的に理解できる。
✅ 一貫性(Consistency): コードは一貫性があり、同じような処理や構造が同じような方法で実装されている。一貫性が保たれることで、コード全体が予測可能で読みやすくなる。
✅ 適切な構造(Appropriate Structure): コードは適切な構造を持ち、モジュール化されている。関連する機能が適切にグループ化され、それぞれの機能が分かれた部分に配置されている。
を満たすものをいう。
では、今回の例で上記を満たすためにはどうすればよいだろうか?
適切なコンポーネントの粒度とは?
可読性を高くするには、HTMLの構造と同じ粒度でコンポーネントに切り出すべきである
ぱっと理解できない場合は、OSSのUIライブラリを見てみるとよい。ベストプラクティスがつまっている。今回はnpmパッケージのMaterial UI
を見てみよう。
code sandbox List.tsxのDemo
見慣れたHTMLの構造に近いからとても見やすい。propsの値から結果も想像しやすい。
無駄なdivが間に入っていないから見ていて気持ちよい。
コンポーネントの作成
では、今回の例に適用してみよう!
Material UIにならって、下記の単位で分割するとよさそうだと分かる。
<ul> ①リスト
<li> ②リストアイテム
<img></img> ③アイコン
<p> ④本文など
<b><b>
</p>
</li>
</ul>
↓
<ScientistProfileList> ①リスト
<ScientistProfileListItem> ②リストアイテム
<ScientistProfileListItemIcon /> ③アイコン
<ScientistProfileListItemSummary /> ④本文など
</ScientistProfileListItem>
</ScientistProfileList>
直感的に理解しやすいコンポーネント名
コンポーネント名もMaterial UIのようにつけると、
- 「アイコン」「サマリー」など区分が分かりやすい。
- 「Listの中でItemを、Itemの中でItemIconを使う」という意図を直感的に理解しやすい。
XxxYyyList
XxxYyyListItem
XxxYyyListItemIcon
XxxYyyListItemSummary
ちなみにScientist
だけだと意味が広過ぎるので、X
のY
ぐらいの粒度で命名するとよさそう。
今回の例では、サイエンティスト
のプロフィール
となった。
ScientistProfileList
ScientistProfileListItem
ScientistProfileListItemIcon
ScientistProfileListItemSummary
対応関係が明確になった!
見づらい例
個人的に見づらい例を挙げておく。
❌見づらい例1: <li>
のままの箇所がある
一瞬びっくりする + 視線が行ったりきたりする
<ScientistProfileList>
<li>
<ScientistProfileListItemIcon />
<ScientistProfileListItemSummary />
</li>
</ScientistProfileList>
❌見づらい例2: List内にdiv混入しているケース
なんだこのdivは?と脳のリソースがもってかれる。
<ScientistProfileList>
<ScientistProfileListItem>
<div>
<ScientistProfileListItemIcon />
</div>
<div>
<ScientistProfileListItemSummary />
</div>
</ScientistProfileListItem>
</ScientistProfileList>
コンポーネントの再利用性について検討する
どこにファイルを配置するか?
この画面でのみ使うものは、同一階層配下にファイル(orフォルダ)を作成したい。
画面が不要になったらフォルダ単位で削除したいからである。
あと、他の画面を開発しているときに、関係のない画面のファイルを見たくない。
/components
|-/scientists
|-/index.tsx
|-/scientistList.tsx
|-/scientistListItem.tsx
|-/scientistProfileListItemIcon.tsx
|-/scientistProfileListItemSummary.tsx
基本的に、atomsのような抽象的なパーツはatomsに切り出す
ただし、atomic designを採用しているからといって、その画面と1:1になっているファイルを別フォルダに移したくない
ファイルを追いづらいし、ファイルを開きづらい(F12で定義ファイルへジャンプできるけど、脳への負荷が上がる)
❌フォルダがバラバラなので負荷が上がる例
/components/organisms/scientists/scientistList.tsx
/components/molecles/scientists/scientistListItem.tsx
🙆♂️機能フォルダ配下にフォルダを作るならまだ見やすい
/components/scientists/organisms/scientistList.tsx
/components/scientists/molecles/scientistListItem.tsx
まとめ
今回の例だと各コンポーネント内の処理がほぼないため、分割しなくてもよいのでは?と感じるかもしれない。
べた書きで実装した方がファイル数が増えないし。HTMLは表示さえできばいいよ!という方針で書いている人が多い。
いずれファイルに切り出していくフェーズで、この分割方法が必要になってくる。私自身、現場で必要になったので説明してみた。
この記事を良いね👏と思ったら、
いいね!をお願いします!
Discussion
例だと誤解を生みそう
本当に汎用的に使われるなら細かい粒度でも良いけど、そうじゃないなら適切じゃないと思う
自分だったら、storybookベースでどの単位で観るべきかでコンポーネントに切り出すかな
コメントありがとうございます!勉強になります。
この記事は参考としての1例を説明しています。
「コンポーネントに切り出したけど、この例だと中身ほぼないし複数回使ってないやん!」とは思いました。
要件に応じて、汎用性や粒度を考えたいですね!