Firebase+React+Reduxで多機能チャットを実装しよう【リアルタイムチャット~React編~】

# Firebase&React&Reduxで多機能チャットを実装しよう【リアルタイムチャット編】

今回から本格的にReactとReduxを触っていきます。
一気に難易度が上がるので覚悟してください！笑

**ガイド**

- [環境準備編](https://qiita.com/torahack/items/a6a5ca9d92cb805b5de5)
- [リアルタイムチャット~React編~](https://qiita.com/torahack/items/56cd0b3f0c9cefeca61b)　←イマココ
- リアルタイムチャット~Redux編~
- ログイン機能
- チャットルーム選択機能
- チャットルーム設定機能
- [デモページ](https://react-chat-28bf1.firebaseapp.com/)
- [GitHub](https://github.com/deatiger/react-chat)

## ディレクトリ構成

前回までのおさらい。
ディレクトリ構成は現在以下の通りです。

要らないファイルを削除して、ディレクトリを追加作成します。

```:Before
react-chat
 │- build/
 │- public/
 │   │- favicon.ico
 │   │- index.html
 │   │- logo192.png <-- REM
 │   │- logo512.png <-- REM
 │   │- manifest.json
 │   └  robots.txt
 │- src/
 │   │- firebase/
 │   │   │- config.js
 │   │   └  index.js
 │   └  index.js
 │- node_modules/
 │- .firebaserc
 │- database.rules.json
 │- firebase.json
 │- package.json
 │- package-lock.json
 └  storage.rules.json
```

```:After
react-chat
 │- build/
 │- public/
 │   │- favicon.ico
 │   │- index.html
 │   │- manifest.json
 │   └  robots.txt
 │- src/
 │   │- components/ <-- ADD
 │   │- containers/ <-- ADD
 │   │- firebase/
 │   │   │- config.js
 │   │   └  index.js
 │   │- templates/ <-- ADD
 │   │- index.js 
 │   └  style.css <-- ADD
 │- node_modules/
 │- .firebaserc
 │- database.rules.json
 │- firebase.json
 │- package.json
 │- package-lock.json
 └  storage.rules.json
```

## React基本ファイルの準備
create-react-appで作成した開発環境の基本を簡単に説明しておきます。

- `public`：雛形となるhtmlファイルを格納するディレクトリ
- `src`：コンパイル前のソースコードを格納するディレクトリ
- `npm run build`：`src`配下のファイルをコンパイルするコマンド
- `build`：コンパイルされたファイルを格納するディレクトリ

### 雛形となるHTMLファイル
雛形となるHTMLファイルには以下を記載します。

- サイトのタイトルやメタ情報
- Firebaseに必要なスクリプト
- Reactコンポーネントをrenderするルートdiv

```html:public/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Firebase React Chat!</title>
    <!-- update the version number as needed -->
    <script defer src="/__/firebase/7.5.1/firebase-app.js"></script>
    <!-- include only the Firebase features as you need -->
    <script defer src="/__/firebase/7.5.1/firebase-auth.js"></script>
    <script defer src="/__/firebase/7.5.1/firebase-database.js"></script>
    <script defer src="/__/firebase/7.5.1/firebase-messaging.js"></script>
    <script defer src="/__/firebase/7.5.1/firebase-storage.js"></script>
    <!-- initialize the SDK after all desired features are loaded -->
    <script defer src="/__/firebase/init.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
```

### Reactコンポーネントをindex.jsファイル
`npm run build`でコンパイルする際に参照されるjsファイルです。
細かい説明は省きますが、後ほど登場するファイルたちの情報をまとめておきます。

```jsx:src/index.js
import React from 'react';
import * as ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {RootContainer} from './containers';
import {configureStore} from './modules';
import './style.css'

ReactDOM.render(
    <Provider store={configureStore()}>
        <RootContainer />
    </Provider>,
    document.getElementById('root')
);
```

`ReactDOM.render(element, container)`で、Reactコンポーネント(element)を雛形HTMLのDOM(container)にレンダーして表示させています。

先ほど作成したHTMLファイル内の、idがrootのdiv要素にレンダーさせているということですね。

それでは、レンダーされるコンポーネントを作っていきましょう。
## Reactコンポーネントの作成
ReactをReduxと共に用いる場合、コンポーネントは2つに分けるのが一般的です。

1. *Presentational Components* --> 見た目(View)を担当する。「コンポーネント」と呼ばれる
2. *Container Components* --> ロジック（振る舞い）に関わる。「コンテナー」と呼ばれる。

より詳細な定義は[コチラ](https://qiita.com/ShiratoriTenta/items/7b4d2ea1a4d1d2336032)の記事を参考にしてください。

上記を踏まえて、[ディレクトリ構成](#ディレクトリ構成)で追加したディレクトリに格納するファイルの役割は以下の通りです。

- `components`: 最小構成要素(パーツ)となる*Presentational Components*、子コンポーネントです。
- `templates`: `components`内のパーツを組み合わせた*Presentational Components*、親コンポーネントです。
- `containers`: `templates`の*Presentational Components*と"状態"を紐づけるための中継役、*Container Components*です。

ログインページを例にすると
`components`は「ボタン」や「テキストボックス」
`templates`は「ボタン」や「テキストボックス」を組み合わせた「ログインフォーム」
`containers`はログイン処理後の見た目(View)がどのように変化するのか定義

少し難しいですよね。

「コンポーネントは見た目(View)のみを持ち、状態(State)と分離させる」というReduxの設計思想を実現するための構成だと思ってください。

それでは、実際にリアルタイムチャットを実装するためのファイルを書いていきます。
### componentsファイルの作成
チャットを実装するためには以下のパーツが必要だと考えました。

1. メッセージ表示エリア
2. メッセージ入力エリア
3. メッセージ送信ボタン

この記事では２と３について解説します。

当記事では[Material-UI](https://material-ui.com/)を使っていますが、詳しい解説を割愛します。
デザインのカスタマイズには癖があるけど、それっぽいUIが簡単に作れて便利だゾ。

また、デモページを作った都合上、独自CSSを使っています。
CSSはGithubのソースコードから確認してください。(`src/style.css`)に記述しています。

```jsx:src/components/Chat/TextInput.js
import React from 'react';
import TextField from '@material-ui/core/TextField';

const TextInput = (props) => {
    return (
        <form className="p-chat__textarea c-grid-center" noValidate autoComplete="off">
            <TextField
                id="standard-text"
                className="c-grid-full"
                margin="normal"
                label="メッセージを入力..."
                multiline
                rowsMax="4"
                onChange={e => props.onChange(e.target.value)}
                value={props.value}
            />
        </form>
    );
};

export default TextInput;
```

```jsx:src/components/Chat/SendButton.js
import React from 'react';
import Button from '@material-ui/core/Button';
import SendIcon from '@material-ui/icons/Send';

const SendButton = (props) => {
    return (
        <Button
            variant="contained"
            color="primary"
            className="p-chat__button-send"
            onClick={() => props.onClick(props.value, props.roomId, props.fromId, props.toId, props.userIds)}>
          <SendIcon />
        </Button>
    );
}

export default SendButton;
```

それぞれのポイントを解説します。

両コンポーネントは、*Stateless Functional Components*という形式で宣言されています。

初期のReactでは、Classを用いてコンポーネントを宣言していました。
近年のReactでは、*Stateless Functional Components*、つまり"関数"として宣言することが推奨されています。

*Stateless Functional Components*には以下のメリットがあります。

- constructorを使わなくて良い
- thisが必要ない
- 状態を持たない（stateless）コンポーネントにできる

つまり、コードがシンプルになってハッピーってことですね☆

また、ES6のアロー関数を使ってさらにシンプルに書いています。

そして重要なのが、関数の引数に渡している`props`です。
この部分`const TextInput = (props) => {`です。
この部分`const SendButton = (props) => {`ですよ。

`props`は親コンポーネントから渡された引数を一挙に受け取ります。
親コンポーネントから`value`として渡した引数を、子コンポーネントで`props.value`のように参照することができます。

`props.onClick`や`props.onChange`は、状態を変更するための*Actions*を呼び出します。
*Actions*については[次の記事]で解説します。

### templatesファイルの作成
`templates`ディレクトリ配下に`Chat.js`を作成します。
`Chat.js`は親コンポーネントとして、先ほど作成したコンポーネントをまとめます。

ファイルが長いので部分ごとに解説します。

#### import
ファイル冒頭のimport文です。

```jsx:src/templates/Chat.js
import React, {Component} from 'react';
import {Chat, Common} from '../components';
import {database} from '../firebase/index'
```

`components/index.js`で`export`した"Chat"と"Common"のコンポーネントを`import`します。

#### constructorとrender

```jsx:src/templates/Chat.js
class ChatTemplate extends Component {
    constructor(props) {
        super(props);
    }

    /* 中略 */

    render() {
        return (
             <div className="p-chat">
                <Common.NavBar
                    value={this.props.messages}
                    actions={this.props.actions.messages}
                    back={this.props.actions.messages.backToRooms}
                    configure={this.props.actions.messages.configure}
                    signOut={this.props.actions.messages.signOut}
                />
                <div className="p-chat__area" id="scroll-area">
                    {this.props.messages.msgs.map((m, i) => (
                        <Chat.AlignItemsList key={i} msgs={m} />
                    ))}
                </div>
                <div className="c-grid__row">
                    <Chat.TextInput
                        onChange={this.props.actions.messages.change}
                        value={this.props.messages.value}
                    />
                    <Chat.SendButton
                        onClick={this.props.actions.messages.submit}
                        value={this.props.messages.value}
                        roomId={this.props.messages.roomId}
                        fromId={this.props.messages.userId}
                        toId={this.props.messages.partnerId}
                        userIds={this.props.messages.userIds}
                    />
                </div>
            </div>
        );
    }
}

export default ChatTemplate
```

親コンポーネントでは`componentDidMount()`などのライフサイクルを使いたいので、*Stateless Functional Components*ではなく*Class Components*で宣言します。

constructorの宣言をします。
`this.props`でreduxのStoreに保存されているGlobal Stateを参照できます。
（ごめんなさい、Storeについても[次の記事]で解説します...！）

```jsx:src/templates/Chat.js
constructor(props) {
    super(props);
}
```

続いて、`render`部分です。
`return()`のなかにDOMやコンポーネントを記述します。

```javascript
render() {
    return (
        /*
            レンダーするDOMを記述する。
            作成した子コンポーネントもこの中で宣言して使う。
        */
    )
}
```

"Chat"としてimportした子コンポーネントを呼び出す方法は以下の通りです。
例...TextInputコンポーネントを呼び出す。

```javascript
return(
    <Chat.TextInput />
)
```

さらに、引数を渡してみましょう。

```jsx:src/templates/Chat.js
return(
    <Chat.TextInput
        onChange={this.props.actions.messages.change}
        value={this.props.messages.value}
    />
)
```

少し分かりづらいかもしれませんが

- `this.props.actions.messages.change`という*Actions*を`onChange`としてTextInputコンポーネントに渡している
- `this.props.messages.value`というGlobal Stateを`value`としてTextInputコンポーネントに渡している

これによって、TextInputコンポーネントでは`props.onChange`や`props.value`として渡された値を参照できる。

#### ライフサイクルメソッド
Reactの*Class Components*ではライフサイクルメソッドが使えます。
ライフサイクルメソッドが分かりやすく図解されている記事は[コチラ](https://qiita.com/kawachi/items/092bfc281f88e3a6e456)

代表的なメソッドは以下。

- `componentWillMount()` --> 現在は非推奨なので使わない。
- `render()` --> Viewを描画する。
- `componentDidMount()` --> API連携など、通信が必要な処理はここで。
- `componentDidUpdate()` --> stateが変更されて再render()が走った後に
- `componentWillUnmount()` --> `componentDidMount()`で確保したリソースを解放する

`src/templates/Chat.js`では各メソッドで以下の処理を実行している。

- `componentDidMount()` --> Firebase DBにメッセージのデータが追加されたらViewを再描画するようにリスナーを仕掛ける。
- `componentDidUpdate()` --> メッセージ一覧の最下部にスクロールする。
- `componentWillUnmount()` --> Firebase DBへのリスナーを解除する。

ソースコード載せると長〜いので、Githubを確認してください🙏

リスナーの設定は`firebase.database().ref().on()`で。
逆にリスナーの解除は`firebase.database().ref().off()`です。

リスナー設定/解除の詳細はFirebaseクライアントSDKのドキュメントを参照。
[データの取得｜Firebase Realtime Database](https://firebase.google.com/docs/database/admin/retrieve-data?hl=ja)

### containersファイルの作成
最後に*Container Components*を作成します。

```jsx:src/containers/Chat/Main.js
import ChatTemplate from '../../templates/Chat';
import {bindActionCreators, compose} from 'redux';
import {connect} from 'react-redux';
import {actions} from '../../modules/chat/index';

const mapStateToProps = state => {
  return {
    messages: state.messages,
  };
};

const mapDispatchToProps = dispatch => {
  return {
    actions: {
      messages: bindActionCreators(actions.messages, dispatch),
    },
  };
};

export default compose(
  connect(
    mapStateToProps,
    mapDispatchToProps
  )
)(ChatTemplate);
```

すでに説明した通り、*Container Components*の役割は中継役です。

ReactとReduxを繋げる`react-redux`というライブラリの`connect`メソッドを使います。

`connect`メソッドは、以下2つをReactコンポーネントで参照できるようにしているイメージです。

- `mapStateToProps`： Reduxで管理しているstate(状態)
- `mapDispatchToProps`： state(状態)を変更するためのReduxの*Actions*

上記の*Container Components*の書き方は汎用的に使えるはずです。

## まとめ
Reactだけならまだしも、Reduxが絡むと途端にややこしくなります。

特に*Container Components*で`connect`するあたりは、最初何をやっているのか全く理解できませんでした。

~~分からなくてもコピペしておきましょう。~~
この記事で少しでも理解を深めていただければ幸いです。

Reactは公式のドキュメントが充実しているので、（自戒も込めて）よく読んだ方がいいですね。

[次の記事]でははいよいよReduxの*Actions*, *Reducers*, *Store*などを解説していきます。