【Firebase】ReactプロジェクトでFirebaseを扱う

<!-- ===================================================== -->
#概要
<!-- ===================================================== -->
Reactのプロジェクトで、Firebaseを扱う方法をまとめます。
※ 処理をいくつかピックアップして記載していますが、ドキュメントが充実しているので、そちらを参照したほうがいいかもしれないです。

この記事は、以下のコースの受講に伴って、備忘録として書いています。詳しく知りたい方は、是非受講してみてください。

https://www.udemy.com/share/103MN4AEYdcVlbTX4D/

<!-- ===================================================== -->
#環境
<!-- ===================================================== -->
* Windows10
* VSCode
* npm

<!-- ===================================================== -->
#セットアップ
<!-- ===================================================== -->

###Firebaseプロジェクトの作成
1. Firebaseの[コンソール](https://console.firebase.google.com/)から、プロジェクトを追加します。
2. アプリの種類を選択します。Webサービスを作成する場合は </> を選択します。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/76f5dfc6-9be7-a825-c2ed-c20a9f4ffce9.png)
3. ⚙ - [プロジェクトを設定]で、SDKの設定と構成を確認しておきます。
![無題1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/172e729f-b51b-5055-2cc9-4f3f919013b5.png)


###インストール
VSCodeでアプリケーションのプロジェクトを開き、ターミナルで以下を実行します

```:Reactプロジェクトの作成
npx create-react-app . --template typescript
```

```:firebaseのインストール
npm i firebase
```
###設定ファイルの作成
1. プロジェクト直下に、環境変数ファイル`.env`を作成します。

![無題2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/e9d399f0-072e-35b6-7f68-1e33218a7f15.png)

```:.env
REACT_APP_FIREBASE_APIKEY="apiKey"
REACT_APP_FIREBASE_DOMAIN="authDomain"
REACT_APP_FIREBASE_DATABASE="https://PROJECT_ID.firebaseio.com"
REACT_APP_FIREBASE_PROJECT_ID="projectId"
REACT_APP_FIREBASE_STORAGE_BUCKET="storageBucket"
REACT_APP_FIREBASE_SENDER_ID="messagingSenderId"
REACT_APP_FIREBASE_APP_ID="appId"
```
Reactプロジェクトの場合、**REACT_APP_**としていすることで、自動的に環境変数として扱われます。
値は、Firebaseコンソールの`SDKの設定と構成`で確認したものを入力します。
REACT_APP_FIREBASE_DATABASE はコンソールに表示されていないので、アドレスの PROJECT_ID を置き換えた値を入力します。

2. src配下に、環境変数を読み込んで、使用するインスタンスを返すファイルを作成します。

```firebase.ts
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
import 'firebase/storage';

const firebaseConfig = {
	apiKey: process.env.REACT_APP_FIREBASE_APIKEY,
	authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
	databaseURL: process.env.REACT_APP_FIREBASE_DATABASE,
	projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
	storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
	messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID,
	appId: process.env.REACT_APP_FIREBASE_APP_ID
};

const firebaseApp = firebase.initializeApp(firebaseConfig);

export const db = firebaseApp.firestore();
export const auth = firebaseApp.auth();
export const storage = firebaseApp.storage();
export const provider = new firebase.auth.GoogleAuthProvider();
```
ここでは、データベース（db）、認証（auth）、ストレージ（storage）、googleアカウント認証（provider）のインスタンスをexportしています。


<!-- ===================================================== -->
#[認証](https://firebase.google.com/docs/auth)
<!-- ===================================================== -->

###認証の設定
Firebaseコンソールの [Authentication] で、使用する認証方法を有効にします。
![無題3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/9aee79b0-1b4c-dce4-4186-bf1be3a45b9c.png)

###ログイン・ログアウトの処理

```tsx:ログイン
const signInEmail = async () => {
	await auth.signInWithEmailAndPassword(email, password);
};
```
[ドキュメント：ログイン](https://firebase.google.com/docs/reference/js/firebase.auth.Auth?hl=ja#signinwithemailandpassword)

```tsx:新規登録してログイン（レジスター）
const signUpEmail = async () => {
	await auth.createUserWithEmailAndPassword(email, password);
};
```
[ドキュメント：レジスター](https://firebase.google.com/docs/reference/js/firebase.auth.Auth?hl=ja#createuserwithemailandpassword)

```tsx:Googleアカウントでログイン
const signInGoogle = async () => {
	// ポップアップでgoogleのサインインを表示する
	await auth.signInWithPopup(provider).catch(err => alert(err.message));
};
```
[ドキュメント：Googleログイン](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider?hl=ja)

```tsx:ログアウト
auth.signOut()
```
[ドキュメント：ログアウト](https://firebase.google.com/docs/reference/js/firebase.auth.Auth?hl=ja#signout)

###ユーザーに関する処理（あれこれ）
こちらの **[ドキュメント](https://firebase.google.com/docs/auth/web/manage-users)** では、以下の操作をする場合の説明があります。

* ユーザーを作成する（上記のレジスター）
* 現在ログインしているユーザーを取得する
* ユーザーのプロフィールを取得する
* ユーザーのプロバイダ別のプロフィール情報を取得する
* ユーザーのプロフィールを更新する
* ユーザーのメールアドレスを設定する
* ユーザーに確認メールを送信する
* ユーザーのパスワードを設定する
* パスワードの再設定メールを送信する
* ユーザーを削除する
* ユーザーを再認証する

<!-- ===================================================== -->
#[ストレージ](https://firebase.google.com/docs/storage/web/start)
<!-- ===================================================== -->
###ストレージの設定
Firebaseコンソールの [Storage] から、デフォルトストレージを作成します。
![無題4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/d0c3fb9f-b1c6-db4d-bba7-f5df7ce6658b.png)

###アップロード・保存したファイルのパスの取得

```tsx:アップロード
// testフォルダに画像を保存する（フォルダがない場合は自動で作成される）
await storage.ref(`test/${fileName}`).put(inputImage);
```
こちらの例では、ストレージの`test`フォルダを参照し（ない場合は作成し）、`fileName`という名前で`inputImage`をアップロードします。
`fileName`のファイルがすでにストレージに存在する場合は上書きされるので、一意な名前をつける必要があります。（Tips参照）

```tsx:URL取得
url = await storage.ref('test').child(fileName).getDownloadURL();
```


<!-- ===================================================== -->
#Tips
<!-- ===================================================== -->
###疑似的な一意な文字列を作成する
```.tsx
let S = [...Array(26)].map((_, i) => String.fromCharCode(i + 97)).join(''); // a-z
S += S.toUpperCase(); // A-Z
S += '0123456789'; // 0-9
const N = 16; // 文字数
const randomChar = Array.from(crypto.getRandomValues(new Uint32Array(N)))
		.map(n => S[n % S.length])
		.join('');
```
この例では、`a-z A-Z 0-9`の文字列に対し、ランダムに16個を連結した文字列を作成します。