2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have 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

2
4
0

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
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?