🤖
Firebase functions
関数の中身の見方
- Firebaseコンソールからfunctions管理画面へ
- 三点リーダから「使用状況の詳細な統計情報」
- 開いた Cloud Functions からソースタブへ
- [zipでダウンロード]
カスタムフック不要!! Reactとオブジェクト指向で完全state管理
Reactは意外とオブジェクト指向と相性がいいです
状態管理ライブラリが不要になります
- 不要: Redux、Jotai、Zustand、TanStack Query
- カスタムフックも不要になります。
用語
- Model: User、Media、Comment、Room、ChatRoomといったエンティティです。
- 階層型オブジェクト: users[].medias[].comments[] といったエンティティらをチェーンで繋げられるインスタンスのことです。
実装
Auth.js
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
} from 'firebase/auth';
import { doc, setDoc } from 'firebase/firestore';
import { auth, db } from './config';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default class Auth {
// Rule of Hooksに則り、**constructor内でのみ**フックを使用します。
// よって、newは トップレベル もしくは Reactの関数から のみできます。(利用側参照)
constructor() {
[this.state, this.setState] = useState();
this.router = useRouter();
}
getStateLogined() {
if (this.state === undefined)
return 'unloaded';
else if (this.state === null)
return 'unlogined';
else
return 'logined';
}
async signUpWithEmail({ email, password }) {
return await createUserWithEmailAndPassword(
auth,
email,
password
).then(async (userCredential) => {
await this.pushUser(userCredential.user, password);
return userCredential.user;
});
}
async loginWithEmail({ email, password }) {
return await signInWithEmailAndPassword(
auth,
email,
password
);
}
async logout() {
await signOut(auth);
this.router.push('/');
}
// TODO: Usersクラスにおけそうである
async pushUser(user, password) {
const docRef = doc(db, 'users', user.uid);
// document の追加
return await setDoc(docRef, {
uid: user.uid,
email: user.email,
password,
});
}
}
AppProvider.jsx
const AppProvider = ({ children }) => {
// 再レンダーされてnewを呼ばれても、(useState同様、)内部のstateは保持されている。
const auth = new Auth();
// useEffectやonClickなど非同期処理内で: () => {
auth.logout(); // 自動でホーム画面に飛んでくれます
}
return <AppContext.Provider value={{ auth, mine }}>{children}</AppContext.Provider>
stateを持たないクラスは?
逆に、"絶対にuseStateを持たない"って決めたクラスなら、
利用側は一度だけnewされるよう固定してあげる必要があります。
(再レンダーに巻き込まれて何度もnewしない為です。)
AppProvider.jsx
const AppProvider = ({ children }) => {
const [xxx] = useState(() => new Xxxx());
const [queryClient] = useState(() => new QueryClient())
課題と解決
非同期でfetchしてくるような子孫stateは 、もちろんuseStateが使えない (副作用内でuseStateは使えないから)。
案:
トップレベルのみstateを持ち、子孫の更新はトップレベルで行う
毎回トップレベルのsetStateを叩いて、負荷はどのくらいあるのだろう...
Discussion
失礼します。
React には Rules of Hooks という規約があるので、このように「コンストラクタの中でフックを呼び出して、返り値を、そのオブジェクトのプロパティとして保存する」べきではありません。
特に、「そのオブジェクトのプロパティとして保存する」ということをしてしまうことで、「State as Snapshot」と呼ばれる React の原則を無視することになり、予測できない挙動の原因になります。
言い換えると、React は「この関数はいつ呼び出され、この式はいつ評価されるのか」が大事なので、「オブジェクト指向」に覆い隠さずに、きちんと分かるように、愚直に書くべきです。(そして、愚直な書き方でカプセル化する方法こそが「カスタムフック」なのです)
実は、React とともに使うライブラリがクラスを使用するケースもありますが、これらは「React のコンポーネントのライフサイクル」と「自作クラスのライフサイクル」をキチンと分離していることに注意が必要です。
以下のように呼び出すことで、「ステートからは独立したもの」として、複雑な状態管理を隠蔽するオブジェクト(
QueryCleitnt
)を扱うことが可能になります。ありがとうございます!勉強になりました!👀
いいえ、少しでもお役に立てたなら幸いです!