LoginSignup
4

More than 1 year has passed since last update.

React+Typescript+Firebase v9

Last updated at Posted at 2021-09-06

はじめにの前の結論

Firebase ver.9 を使って React で認証を実装した。

はじめに

紆余曲折あって、React の認証機能を Firebase に外出しした。ググれば余裕っしょと高を括っていたが、タイミング良く?悪く?Firebase の Node Module が 8 から 9 にバージョンアップした。ドキュメントを見ると、9はまだベータ版と書いてあるが、npm/yarn して入るのは、今日現在であれば、9.0.1 でそれが Latest となっているので、もう 9 なのだろう。バージョンアップガイドを見ると、

バージョン9の利点と限界
完全にモジュール化されたバージョン9には、以前のバージョンに比べて以下のような利点があります。

  • バージョン9では、アプリのサイズを大幅に縮小することができます。最新のJavaScriptモジュール形式を採用しており、アプリに必要なアーティファクトだけをインポートする「ツリーシェイク」が可能です。アプリケーションにもよりますが、バージョン9でツリーシェイクを行うと、バージョン8で構築した同等のアプリケーションに比べて、80%のキロバイト削減が可能です。
  • バージョン9は今後も継続的な機能開発が行われますが、バージョン8は将来の時点で凍結されます。

とあるので、今後は 9 系統を使えば良かろうと判断した。が、いかんせん出たてホヤホヤなので、やってみたドキュメントがない。私のような日本語で教えて君にはつらいタイミングだ。8 の書き方だと、9 では動かない。面倒くさいなと思ったが、ベトナムは4連休だったので、トライしてみた。

作りたいもの

Screen Shot 2021-09-06 at 2.08.41 PM.png
こんなようなものを作ることを目指した。割と汎用的な構成ではないかと思う。

最終的にできたものは、この図の Home にログアウトボタンを追加したのと、Facebook で登録ボタンは、Facebook でログインボタンと同じ機能になった。Firebase に Facebook で登録という機能はない(ログインすると登録される)。実際のアプリケーションでは、その他必要な情報を取得することもあるかと思うので、処理が分かれるかも知れないが、今回の例では必要ないので、省略した。

バージョン情報

  • React: 17.0.2
  • Firebase: 9.0.1
  • react-router-dom: 5.3.0

インストール手順は、こんな感じ

$ npx create-react-app app --template typescript
$ cd app
$ yarn add react-route-dom firebase
$ yarn add --dev @types/react-router-dom @types/firebase

$ npx browserslist@latest --update-db

最後のは、yarn start したらやれと言われたのでやった。
リポジトリのルートディレクトリには、docker 関連等を置きたかったので、一段下げた。

実装方針

  1. 素の Router を書き、各ページを作成・表示できるようにする
  2. 各ページのデザイン(html+css)
  3. Firebase を利用しない簡単な認証機能(メールとパスワードが一致するかだけ)の実装
  4. 未認証の場合、ログイン画面にリダイレクトする PrivateRoute の実装
  5. Firebase 注入
  6. 認証後、リロード時にログイン画面に戻ってしまう件修正
  7. Facebook ログインが Localhost に厳しい件の暫定対応

その他の方針としては、Type はきっちり明示、CSS FW は使わない、Atomic design の方向で、くらい。

出来上がったものは、こちら
kurab/react-typescript-firebase

なお、エラーメッセージ表示などは、ほとんどやってない。alert が出るだけ。+ React.js は経験2〜3ヶ月のぺーペーなので、至らないところはゴメンナサイ。

実装

手順の5以外は、今回の本質ではないので、サラッと。

1. 素の Router を書き、各ページを作成・表示できるようにする

Routing の基本的な処理は、

app/src/router/Router.tsx
app/src/router/AuthRouter.tsx

app/src/App.tsx

で、react-router-dom で実装している。完成形は、素ではなく、PrivateRoute や Provider などが入っている。
各ページは、

app/src/components/pages/*

2. 各ページのデザイン(html+css)

前述の app/src/components/pages/* に必要な要素を配置し、CSS は、app/src/assets/styles/* にて scss で書いた。scss の build? compile? は VS Code 任せ。React っぽい書き方ではないと思うが、個人的にまだピンと来ていないというか、しっくり来るやり方が見つかっていないので、こんな感じで書いている。

実際の画面は、デザインに結構忠実なのでスクリーンショットは省略。

3. Firebase を利用しない簡単な認証機能(メールとパスワードが一致するかだけ)の実装

完成形では消えてしまっているが、

app/src/hooks/useAuth.ts
app/src/hooks/useLoginUser.ts
app/src/providers/LoginUserProvider.tsx

に書いてある(あった)。出来上がった Provider で Router の 404 ページ以外を囲っている。
app/src/types/api/User.ts.bk は、この時点の名残り。なんか上がっちゃってた。useAuth に user 配列をハードコードして if 文書いてただけ。

form の値の取得は、色々なやり方があると思うが、私は ChangeEvent<HTMLInputElement> で取得し、useState を使って利用している。Register 画面のパスワード2回のチェックは、同じにならなければ、Register ボタンを押せないようにするという手抜き実装。細かくエラーメッセージ出したりする場合は、もうちょっと考える必要があるが、今回はこれで。

4. 未認証の場合、ログイン画面にリダイレクトする PrivateRoute の実装

PrivateRoute は、

app/src/router/PrivateRoute.tsx

もっとスマートな書き方もあるのかと思うけど、見た目の分かりやすさ優先。それを使って、 app/src/router/Router.tsx を書き換えた。

6. 認証後、リロード時にログイン画面に戻ってしまう件修正

app/src/providers/LoginUserProvider.tsx

の useEffect あたりが、それ。

7. Facebook ログインが Localhost に厳しい件の暫定対応

Facebook ログインは、Redirect URI が SSL でないと機能せず、Localhost に厳しい。Firebase チュートリアル記事で Facebook ログインが少ない理由は、これか…な?
という訳で、Docker を使って解決した。

Dockerでローカル開発環境のhttps化

この記事そのまま。Chrome のセキュリティを下げることになるが、この記事にある通り、Localhost なので、許容範囲かと思う。必要ないときは、元に戻しておけば良い。

Firebase9 注入

では、いよいよ Firebase9。と言っても、ドキュメントに全部書いてあるので、それ読んでという感じ。

Firebase の設定、Facebook Dev の設定は、

このあたりを参照した。.env には、Firebase の設定値を書く。

React 内の Firebase の実装は、firebase.js/ts とかを作って…という解説が一般的だが、なんか嫌だったので、認証系処理を行う hook の app/src/hooks/useAuth.ts の中に書いた。

Firebase 8 系と 9 系の違いは、例えばメールとパスワードでユーザ登録する場合、8では、

ver.8
firebase.auth().createUserWithEmailAndPassword(email, password)...

こんな感じになるが、9 の場合、

ver.9
import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";

const auth = getAuth();
createUserWithEmailAndPassword(auth, email, password)...

こんな感じになる。ドットチェインがなくなり、auth = getAuth() して、パラメータとして渡すようになった。全ての関数を比較したわけではないが、概ねこんな感じ。

WebとFirebaseの詳細 にも説明がある。

バージョン9のSDKの大部分は、バージョン8と同じパターンを踏襲していますが、コードの構成は異なります。一般的に、バージョン8は名前空間とサービスのパターンを指向しているのに対し、バージョン9は個別の機能を指向しています。例えば、バージョン8では firebaseApp.auth() のようなドットチェインがありましたが、バージョン9では firebaseApp を受け取り、Authentication インスタンスを返す getAuth() 関数に変更されています。

JavaScript で Facebook ログインを使用して認証する にも、基本的な操作のいくつかの比較があるので参照。

その他のメソッドについては、認証パッケージ に丁寧に書いてあるので、それを見れば解決しないことはないと思われる。

useAuth.ts 内で、useLoginUser から setLoginUser を利用しているが、その実態は、app/src/providers/LoginUserProvider.tsx で定義されている。

LoginUserProvider.tsx
export type LoginUserContextType = {
  loginUser: LoginUser | null;
  setLoginUser: Dispatch<SetStateAction<LoginUser | null>>;
 };
...
  const [loginUser, setLoginUser] = useState<LoginUser | null>(null);

この部分。

ここで、Firebase でログインしたりした時の戻り値の型が必要になるが、API ドキュメントには、UserCredential が返ってくると約束(可愛い)。

Provider に渡すのは、UserCredential.user だが、型はここを見てもよく分からない。
ので、こんな感じにした。

LoginUserProvider.tsx
import { UserCredential } from "firebase/auth";

type LoginUser = UserCredential["user"];

結局、これで良いのか良く分からないが、typescript が怒らないから(怒られた末こうなったから)、良いのかな?

なお、auth インスタンスは、useAuth.ts の中で initialze した後に作っているが、これを export して使い回す必要はなく、必要な時に、getAuth() すれば良い。

おしまい

セキュアで便利な Auth Provider を自前で作るのは、ぶっちゃけ大変だ。途方に暮れるレベルで大変だ。NextAuth.js 以上のことはやりたくない。Firebase 落ちたらどうすんの?みたいな話は別途考える必要があるとして、Firebase や Auth0 のようなサービスがあるのはとても助かる。要件次第ではあるものの、使える時は使っていきたい。

公式ドキュメントが非常にしっかりしていたので、思ったより、Firebase の実装は簡単だった。今回の実装時間も Firebase 部分は全体の1割くらい。結局、認証機能の実装は、それ以外の部分が複雑で混ぜるとより分かりづらくなるので、関心事は分離して考えるのが良い。

今回のソースコードは、Firebase の設定などを済ませると、ログイン後にうちの可愛いネコが拝める特典付き。おわり。

Screen Shot 2021-09-06 at 4.26.19 PM.png

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
4