はじめに
自分は2年目でフルスタックエンジニアとして実働しています。
ロジックは書けるけど、React特有の作法やコンポーネント設計に自信がない…」という状態から、一歩踏み込んでフロントエンドを「グリップ」するために意識したことをまとめます。
1. Reactのディレクトリ構成の方向性を確認する
チーム開発をやる、または業務のシステムを動かすソースコードには必ず意味があると思っています。
Reactのフォルダ構成は決まっていないので自由度が高いなと思っていましたが、読みやすく・拡張性があって、チーム間での連携もしやすいディレクトリの切り方が存在していました。
基本の構成
src/
├── components/ # UIコンポーネント(Button, Inputなど)
├── hooks/ # ロジックを再利用するためのカスタムフック
├── pages/ # ルーティングに対応する各画面
├── services/ # API呼び出し・ビジネスロジック
└── utils/ # 補助的な純粋関数
機能別構成(Features)
また、機能別にわけるとこんな構成になります。
src配下にfeaturesをきっていくことでわかりやすく、分散して開発をすすめることができます。
src/
├── features/
│ ├── auth/ // 認証機能
│ │ ├── components/
│ │ ├── hooks/
│ │ └── api/
│ ├── products/ // 商品機能
│ │ ├── components/
│ │ ├── hooks/
│ │ └── api/
│ └── cart/ // カート機能
│ ├── components/
│ ├── hooks/
│ └── api/
└──
components配下に、コンポーネントごとの部品を置くことでより部品を再利用しやすくしていました。
components/
│
├── Button/
│ ├── index.jsx
│ ├── index.storires.jsx
├── Header/
│ ├── index.jsx
│ ├── index.storires.jsx
└──
色々なサイトのBestPracitceを参照しましたが、自分の参画しているプロジェクトでは、BestPracticeを元に組み立てられていることを実感しました。
参考
2. 型定義に対してはどう厳密であるべきなのかを整理する
型定義は厳密すぎるくらいがよい。
型指定は厳密であるほうがいいよね。といっても、どこまで厳密である必要があるのか?っていうところがわからなくなっていました。
そのときのレビューをいただいて、
- 「引数になんでも受け取っちゃうものはよくないよね」
- 「何をかいてあるのかわからなくなる」
// 何でも入ってしまう
const role: string = "admin";
type Role = 'guest' | 'standard' | 'admin';
const role: Role = 'admin';
実装当時、any型はだめだとわかっていましたが、実質any型になっていました。
そう。単にstring定義もいいけど、どんな文字列でも代入できちゃう。
→ 特定の値しか受け取らない場合は、String Literal Typesを扱うべき。
参考
3. null判定を呼び出し側で定義したいか?関数のメソッド内で条件分岐を定義するか?
BFF構成でBEからFEへnullable型が渡される場合のチェックを考えていました。
// 関数は「文字列」しか受け取らない(厳格・シンプル)
const formatBio = (bio: string): string => {
return `${bio} (文字数: ${bio.length})`;
};
const user = { bio: null }; // BEからのレスポンス
// ❌ コンパイルでエラーがでちゃう
console.log(formatBio(user.bio));
// 安全だがながい!!
if (user.bio !== null) {
console.log(formatBio(user.bio));
} else {
console.log(formatBio("未設定"));
}
最初はパターン1の書き方で進めていたのですが、nullがFEで渡ってきた場合はどう記述するのか悩んでいたので、迷っていました。
// nullまたはundefinedの時だけ 右側の値を使う
console.log(formatBio(user.bio ?? "未設定"));
そもそもnullをBEで返却するかどうかを検討することが必要だとは思いますが、今回Null合体演算子を呼び出し元で適用するようにしてみたところ、比較的コードがよみやすくなりました。
4. 見た目に関する情報について
- stringに渡されてきたもののをそのままの精度で保持したい
- 3桁ごとにコンマを適用したい
etc...
BigNumberライブラリを導入するにあたって、このあたりを整理していました。
utils フォルダに何でも詰め込んでいたのですが、「数字の整形」と「単位(円)の付与」を混ぜてしまったことで議論になりました。
議論になったコード
export const formatYen = (amount: BigNumber): string => {
return amount.toFormat(3) + "円"; // 「カンマ区切り」と「単位」が合体している
};
改善後
// 1. ロジック(utils)は変換だけに徹する
export const formatComma = (amount: BigNumber): string => amount.toFormat(3);
// 2. 表示(JSX)で単位を付与する
<span>{formatComma(price)}円</span>
円に限ったことでもなく、色や余白もおそらくutils配下にいれると単一責任原則に反するのではないでしょうか?
おわりに
フルスタックといってもバックエンドをメインに実装をしており、ある程度自信をもって開発をしていたのですが、フロントエンドのほうも深く潜ってみると、いろいろな気づきがありました。
- あれnull許容だっけ?
- 値がなかったらどういう値を返すんだっけ?
BEのControllerでFE側の判断基準をしやすくするためにも、このあたりを意識してコードを書く必要があると思っています。
今回をきにチームとしての方針をどうするのか?nullの扱いだったり、型定義だったりがバラバラだったりすることもあるので、チームとしての認識すり合わせや目線のすり合わせも必要になるなと感じることができました。