ぼくのかんがえたさいきょうのReact in 2024
概要
どうも、ukmshiです。最近、フロントエンドの業務が増えてきて、フロントエンドの関する知見が増えてきたので、自分なりに考えた「ぼくのかんがえたさいきょうのReact」を紹介していこうと思います。
なお、専門はクラウドインフラとバックエンドです。フロントエンドに関してははちょっとかじったレベルであり、大した知識もありません。
また、アーキテクチャに関する知見も大してありません。
本記事は
- 極力、学習コストが低く
- 極力、スケールしやすい
を目標として考えた構成となっております。
アーキテクチャ
私がReactの学習にあたり参考にしており、参考にしているのがBulletproof-reactのアーキテクチャです。
採用理由としては
- スケールしやすい
- 学習コストが低い
です。弊社では、小 〜 中規模の開発がメインです。その上で、別の案件でも再利用可能で、学習コストが低く、比較的スケールしやすいという点で、Bulletproofのアーキテクチャを採用しています。
大規模なアプリケーションの場合のスケールしやすさも考えると、DDDとかClean Architectureなどを採用したほうがいいのかもしれませんが、学習コストの高さなどを考え採用はしませんでした。
React学習したてのとき、Bulletproofのおかげで結構成長出来ました。
本稿は基本的にはBulletproofには沿っていますが、Bulletproofで使いづらい部分を、オリジナルで解釈改善しているので、その紹介です。
ディレクトリ構成
下記は基本的なディレクトリ構成です。
src/
├── assets # CSSや画像などの静的コンテンツを格納します
│
├── components # グローバルに扱うコンポーネントを格納します
│
├── config # APIのURLや、固定値などの設定を格納します
│
├── features # 機能ベースのモジュールを格納します
│
├── hooks # グローバルに扱うHooksを格納します
│
├── lib # ライブラリの設定を行い、再度Exportします
│
├── pages # 最終的に描画するページコンポーネントを格納します
│ # URLと同じ階層構造にしましょう
│
├── providers # すべてのアプリで使用するプロバイダーを格納します
│
├── routes # ルーティング設定を格納します
│
├── stores # グローバルに扱う状態管理を格納します
│
├── test # テスト用の機能や、モックサーバーを格納します
│
├── types # アプリケーション全体で使用する基本的な型を格納します
│
└── utils # 共通の関数を格納します
下記はfeaturesディレクトリ構成です。
src/features
└── [awesome-feature]
├── api # 特定の機能に関連するAPIリクエスト宣言とapi hooksを格納します
│
├── assets # 特定の機能でしか使用しな静的コンテンツを格納します
│
├── components # 特定の機能のコンポーネントを格納します
│
├── hooks # 特定の機能のhooksを格納します
│
├── stores # 特定の機能の状態管理を格納します
│
├── types # 特定の機能の型を格納します
│
├── utils # 特定の機能の関数を格納します
│
└── index.ts # 特定の機能のエントリーポイント
# 指定された機能のパブリックAPIとして機能します
# その機能の外部で使用されるすべてのものをエクスポートします
基本的にはBulletproofと同じですが、最終的なページを描画するコンポーネントに関しては、NextJSと同じ、src/pages
に格納しています。
Bulletproofではsrc/[awesome-feature]/routes/
に格納することになっていますが
- モーダルやカードコンポーネントでしか使用しない機能
- urlは存在しても、機能として存在しない
- ディレクトリ構成と、urlが直感的ではない
- コンポーネントとページが同じレイヤーにいるのは気持ち悪い(主観)
などの問題が出てきたため、src/pages
へ変更しました。
これにより、URLとPageコンポーネントが一致して、直感的なディレクトリ構成にしました。
ルーティング
ルーディングは、src/routes
に格納しています。
遅延描画を行うことで、ページを完全に読み込んでから描画されるようにしています。
遅延描画のコード(Bulletproofからのコピーです)
// src/utils/lazyImport.ts
import * as React from 'react';
export function lazyImport<
T extends React.ComponentType<any>, // eslint-disable-line @typescript-eslint/no-explicit-any
I extends { [K2 in K]: T },
K extends keyof I
>(factory: () => Promise<I>, name: K): I {
return Object.create({
[name]: React.lazy(() => factory().then((module) => ({ default: module[name] }))),
});
}
遅延描画の使い方
// src/routes/[awesome-page]/index.tsx
import { lazyImport } from '@/utils/lazyImport';
const { AwesomePage } = lazyImport(() => import('@/pages/AwesomePage'), 'AwesomePage');
export const planRoutes = [
{
path: '/awesome',
element: <AwesomePage />,
}
];
これにより、プロバイダーでReact.Suspense
を指定していると、React.Suspense
で指定した内容を読み込んでる間が描画してくれます。
遅延描画に関するBulletproofのリンク
状態管理
状態管理には、Recoil.jsを採用しています。
採用理由としては、
- Reduxは学習コストが高く、難しい
- useContextでは、柔軟性がなく、ハードコードに成りがち
という課題をよしなに改善しており、使いやすいためです。
詳細は違う記事にお譲りします。
通信
通信で使用しているのは
です。
どれもBulletproofで採用されており、キャッシュなどが扱いやすいので採用しています。
GraphQL関連に関しては、弊社ではほぼ取り扱いがないので割愛します。
命名規則
axios-case-converterに関しては、BEとの命名規則の違いを吸収してくれるのでおすすめです。
弊社はBEにRailsを採用しているため、Railsのレスポンスは基本的にケバブケースですが、JSは基本的にキャメルケースです。
従来は仕方なくJS側でもケバブケースを書く必要がありましたが、axios-case-converterを使用すると、すべてキャメルケースに統一できるのでおすすめです。
エラー処理の共通化
TanStack Query(旧:React Query)で通信のエラーなどを共通で処理するためのWrapperを紹介しておきます。
通信エラーに関して、共通の通知を出したりすることが可能になります。
通知
通知に関しては、状態管理などで共通化し行うのが一般的ですが、弊社は、React-toastifyを採用しています。
- Providerに書き込んでおけば、どこでも呼び出せる
- 無理に状態管理を使用する必要がなくなる
これらの理由で採用しています。
認証・認可
認証
認証にはJWT + RefreshTokenを採用しています。
認証パッケージにはreact-query-authを採用しましたが、滅茶苦茶使いづらいです。
Bulletproofで採用されていると理由で、安易に採用したのが原因なのですが、Bulletproofが書かれた頃はV1で、現在リリースされているのがV2です。
V2になってから、クセが強く個人的には変更したと思っています。
認証代替パッケージ
react-query-authの代わりに、react-auth-kitというパッケージがあるらしく、今度試してみたいと思っています。
認可
認可に関しては、Bulletproofを参考にしつつも、独自の認可処理を実装してます。
詳しくは別記事に書いてあるので、ぜひチェックしてみてください。
その3に実際のコードも載せておきました。
スタイル
スタイルに関しては、もともとCSSコーダーでもあったのでCSSの苦しみを理解しているので、極力学習コストが低く、依存性が低いものがいいと思っており、以下の2つのパッケージを採用しています。
clsxはプログラミング的にスタイルを管理したい場合に扱いやすく、採用しているMUIなどのUIキットを共通コンポーネントでスタイルをlg
, md
, sm
などで固定にした場合に使いやすいため採用しています。
styled-componentsは、ローカルのみに適応できるのがメリットなので採用しています。
CSSやSassでグローバルに宣言すると、複数人で開発したりして、上書きされてしまったり、思わぬところでスタイルが崩れたりするケースを数多見てきました。また、FLOCSSなどのCSS設計を導入すると結構な学習、導入コストを必要とします。それ故、CSSを直接利用するのは個人的には好まないため、styled-componentsを採用しています。
CSSはどこで使っているのか?
CSSやSassを直接利用するケースは、MUIなどのUIキットのスタイルを好意的に上書きしたい場合のみに使用しています。
MUIなどのUIキットを使用している時点で、グローバルにClassを宣言して、スタイルを適応させたいケースは稀です。
- 行いたいスタイルは使用しているUIキットのみで再現できないか
- 再現出来ない場合はグローバルにする必要があるのか
CSSを直接利用したいケースがある場合、その都度、コードレビューで開発者同士で話し合っています。
Formのバリデーション
Formのバリデーションに関しては
を採用しています。
どちらもBulletproofで採用されており、比較的扱いやすいため採用しています。
最後に
ここまで読んでいただき、ありがとうございました。
1月もあっという間ですね。弊社はボーナスが1月なので、気分ウッキウキです。
Discussion