🐈

Reactでオブジェクト指向やってみる #状態管理ライブラリ一切不要

2024/07/06に公開
4

オブジェクト指向を取り入れると

クラス(オブジェクト指向)の代わりにカスタムフックを使うと...

  • 手続き型プログラミングになりがち
  • 故に、関数名が長くなりがち。
  • 人によってカスタムフック名がばらばら
  • 人によってカスタムフックの粒度がまちまち
  • 非同期処理を追うのが大変
  • Reactの再描画に慣れてないとなんでその変数にその値が入っているのか不思議
  • Promise使いづらい
  • テスト書きづらい

Reactにおけるオブジェクト指向のデメリット

  • 配布されてるライブラリさえもカスタムフック useXxxxxx で提供されているものは
    • if文の前に書かないといけない
    • useEffect内で使用できない
    • Reactコンポーネントの外で使えない
    • Hooks of Rule の命名規則に従わないといけない
  • 故に、カスタムフック化されているライブラリは、クラスの中に取り込めない。

Reactとオブジェクト指向の融合

DI(依存性注入)。

ちょっと凝った例でごめんなさい。push通知関係のカスタムフックです。

FCM(Firebase Cloud Messaging)
const useFCM = (mine: User) => {
  const [permission, setPermission] = useState(Notification.permission);
  const [isLoading, setIsLoading] = useState(false);
  const fcm = new FCM({ permission, setPermission, isLoading, setIsLoading });

  useEffect(() => {
    // 初期処理
    if (mine && fcm.isGranted()) {
      fcm.getToken().then((token) => {
        mine.tokens.push({ token });
      });
    }
  }, []);

  ...

  return fcm; // 外部にインスタンスを提供し、自由にpush通知を操作できるようにする

SWRなどのライブラリを使っている場合は、SWR.dataなどの返り値をDIしてあげてください

どうやってこんな綺麗なコードが書けるのか?

責務がスッキリ。
疎結合にしてテストを容易にしています #DI((Dependency Injection))

FCM.ts
import { getMessaging, getToken, onMessage, isSupported } from "firebase/messaging";

export default class FCM {
  messaging: Messaging;
  // DI
  constructor({ permission, setPermission, isLoading, setIsLoading }) {
    this.permission = permission;
    this.setPermission = setPermission;
    this.isLoading = isLoading;
    this.setIsLoading = setIsLoading;
    this.messaging = getMessaging(app);
  }
  isGranted() {
    // 個人的にはこういうのもDRYの法則で隠蔽化。修正範囲を最小限にしています。
    return this.permission === 'granted'; // 'approve'という値も入ってきたとしても
    // たったこの行↑を OR演算子(||) 追加するだけで修正が済みます
    // #保守性 #可読性 #規模が大きいほどに威力増すタイプ
  }
  async getToken(): Promise<string | undefined> {
    ... // 4行くらい
  }
  // INFO: push通知用メソッド
  async pushTo(targets, data: FCMpayLoad) {
    ... // 7行くらい
  }
  async listen() {
    ... // 3行くらい
  }
  async subscribe() {
    ... // 5行くらい
  }
  ... // 他メソッドも追加し放題。パケ放題。            ※2024年
}

仕事の割り振りもかんたん。PMうれぴ。 (テストも同様)

粒度: メソッド単位 / クラス単位 / 機能単位 / 要件単位

Need more sample codes?

Reactのstate管理
const [name, setName] = useState('ハリー・ポッターと秘密の部屋');
const room = new Room({ name, setName });
room.name // stated variable
room.setName('アズカバンの部屋');
API通信系
// メソッドの内部でstateをいじってあげるとViewと同期するようになって、脳汁でる
class Room extends CRUD {...}
room.update({ name: '赤くない部屋' });

class ChatRoom extends Room {...}
chatRoom.messages.create({ title, body, from: User, to: User });
chatRoom.messages[n].update({ title, body }); 

class PrivateChatRoom extends ChatRoom {...}
privateChatRoom.messages[n].update({ title, body });

const data = await room.fetch();
privateChatRoom.delete();

ディレクトリ構成

  • 推奨ディレクトリ: src/model/
    • 例: User.ts
    • 例: Media.ts
    • 例: Comment.ts
    • 例: Thumbnail.ts
    • 例: Room.ts
    • 例: Member.ts
    • 例: List.ts
    • 例: Content.ts

オブジェクト指向のコツ

どこかに移そうと思っている:

オブジェクト指向のコツは主語を見極めること。

(例えば画像編集コンポーネントを作る際、以下のような登場人物(=エンティティ)がある

これらの依存関係を整理して、親となるオブジェクトを見極め、それを主語とすると良い

※サンプルコード欲しければ掲載します

まとめ

手続き型プログラミングして関数の多さに泣いてるひと、コード量が増えて依存関係の見通しの悪さに難読を強いられている&解読している時間のほうが長ぇひとは、まずはいくつかクラス(エンティティ)定義して、グルーピングしちゃいましょう。

長い関数名引数の羅列で悩まされていたあなた、
(sendMessageInPrivateChatRoom(fromUser, toUser, body, title, createdAt) とか笑😂)。
それから大量のif文にテスト書くの疲れた、学生諸君。
そもそもif文が入れ子してインデントの深淵ができてしまう、番台のおじいちゃん。

幸せになれます。

Discussion

llc_starhacksllc_starhacks

お持ち帰り用Code:

/**
 * @ref: https://zenn.dev/llc_starhacks/articles/929e58b503d6ce
 */
class ChatRoom extends Room {...}
chatRoom.messages.create({ title, body, from: User, to: User });
chatRoom.messages[n].update({ title, body });

class PrivateChatRoom extends ChatRoom {...}
privateChatRoom.messages[n].update({ title, body });

const data = await room.fetch();
privateChatRoom.delete();

const [name, setName] = useState('ハリー・ポッターと秘密の部屋');
const room = new Room({ name, setName });
room.name // stated variable
room.setName('アズカバンの部屋');
Hidden comment