Railsチュートリアル幻の15章 React編

社内で利用した、Reactの研修資料を公開します。
Railsチュートリアルの続編として作成したので、Rails上でReactを使うためには...ということが書かれています。Railsチュートリアルテイストになっていますが、一部社内でのコーディング規約や利用するライブラリについての言及があります。

また、研修ということで、ソースコードはGitHubの差分をスクショするという形で掲載しています。コピペできるコードが欲しいという人は、差分表示を作るのに使ったリポジトリを利用して下さい。
https://github.com/kouheiszk/sample_apps/tree/master/5_1_2/ch15

# 第15章 Reactの導入

この章では、サンプルアプリケーションで作ってきたUIパーツをReactというJavaScriptのライブラリを利用してコンポーネント化していきます。具体的には、ユーザのフォロー（及びフォロー解除）できる仕組みを、Reactで置き換えます。まずReactの学習を始める前に、webpackというJavaScriptをビルドする新しい仕組みを導入します（15.1）。webpackの導入が終わったらReactをインストールし、Reactを利用できる環境を作成します(15.2)。その後、Reactとは何かを学ぶために、まず数字を押すとボタンがカウントアップするような簡単なコンポーネントを作り、Reactのコードの記述方法とその動作原理を学びます(15.3)。その後、フォローボタンをReactコンポーネントに置き換え、コンポーネント化とその方法を学びます。ここではAjax通信もReactコンポーネント内で行うようにし、フォロー（及びフォロー解除）を行える完璧なコンポーネントを作成していきます(15.4)。

この章では、本書（Railsチュートリアル）の中で最も幅広い知識が要求されます。コントローラの記述方法やルーティングの指定方法が不安な人は、以前の章をよく復習してから始めましょう。
Railsも5.1からReactを公式にサポートしましたし（4系でも利用することはできます）、JavaScriptでコンポーネントを記述するスタイルは今後のWebフロントエンド開発のスタンダードになっていくと思われます。個人の趣味で作成するような小規模開発でも、大規模な開発でも必ず役に立ちます。本章の最後では、Reactと同時に使われることがあるReduxというフレームワークについて簡単に説明します(15.5)。

# 15.0 開発環境と構築済みのアプリケーション

## 15.0.1 開発環境

Railsチュートリアルの1章に記載のある開発環境を利用します。
https://railstutorial.jp/chapters/beginning?version=5.1#sec-development_environment
Cloud9のRails TutorialのテンプレートではRubyだけではなく、これから説明するNode.jsも利用することができます。

## 15.0.2 アプリケーションの取得

偉大なる先輩が、Railsチュートリアルを14章まですすめてくれています。
本チュートリアルからRailsチュートリアルをReactを学ぶために開始する人は、この14章までチュートリアルが完了しているアプリケーションを利用しましょう。

```sh
$ pwd
/home/ubuntu/workspace
$ git clone https://github.com/yasslab/sample_apps # リポジトリを取得する
$ cd sample_apps/5_1_2                             # Rails5.1.2のチュートリアルに移動
$ cp -r ch1{4,5}                                   # １４章をコピーして15章に
$ cd ch15                                          # コピーした15章のディレクトリに
$ pwd
/home/ubuntu/workspace/sample_apps/5_1_2/ch15
```

## 15.0.3 アプリケーションの動作確認

アプリケーションを起動しましょう。
必要な依存パッケージをインストールします。

```
$ gem install bundler # 必要なら行う
$ bundle install
```

必要であれば、データベースを作成し初期データも投入しましょう。

```
$ rails db:migrate
$ rails db:seed
```

以上で起動の準備が整いました。念のため、テストが現状で全て成功することも確認しておきましょう。

```
$ rails test
```

テストが全て成功することが確認できたら、サーバを立ち上げて、ブラウザからアクセスしてみます。

```
$ rails server -b $IP -p $PORT
```

起動後の画面は下図の通りになるはずです。これでReactを学習する準備が整いました。

![起動後の画面](https://qiita-image-store.s3.amazonaws.com/0/20406/0fa77e29-83d7-fbd4-b132-757fe4516358.png)

# 15.1 webpackの導入

この章ではwebpackを導入していきます。
webpackとは、複数のJavaScriptのファイルを1つにまとめてくれるモジュールバンドラです。webpackを利用しないRailsプロジェクトの場合、この機能をAsset Pipelineが担っています。ではAsset Pipelineでも良いのではと思われると思いますが、webpackを利用すると他にも色々な機能が利用できます。例えばRailsだと、JavaScriptは素のJavaScriptで記述される以外に、CoffeeScriptやあるいはerbなどで記述されます。プロジェクトによってはTypeScriptやElmを使ったりもします。これらのJavaScript以外で書かれたコードを、ブラウザが解釈できるJavaScriptに変換してあげる必要があり、その役割もwebpackが担ってくれます。また、Reactなどをnodeモジュールとして管理したいという場合にも、webpackを利用することで簡単に管理できるようになります。

## 15.1.1 Webpacker gemの追加

まず、GemfileにWebpacker gemを追加します。これは、RailsのアセットパイプラインとNode.jsのwebpackをいい感じに連携してくれるgemです。

```Gemfile
gem 'webpacker', '~> 3.0'
```

次に、いつも通り`bundle install`を実行します。

```sh
$ bundle install
```

## 15.1.2 webpackerのインストール

gemが追加されたら、railsコマンドでwebpackerのインストールタスクを実行し、webpackの設定ファイルなどを生成します。

```sh
$ rails webpacker:install
```

おっと、`yarn`がインストールされていないとエラーがでました。

```sh
$ rails webpacker:install
Yarn not installed. Please download and install Yarn from https://yarnpkg.com/lang/en/docs/install/
```

`yarn`を説明する前に、`npm`を説明しようと思います。
`npm`はNode.jsのパッケージマネージャです。先程`webpacker`を導入する際に利用した`gem`はRubyのパッケージマネージャですが、それのNode.js版という認識で問題ありません。
`yarn`は`npm`と似たパッケージマネージャーですが、インストールするパッケージをキャッシュすることにより`npm`よりも高速に動作してくれるパッケージマネージャです。
詳しくは、`yarn`のホームページに詳しく書かれています。

https://yarnpkg.com/ja/


さて、`yarn`をインストールしましよう。Cloud9の環境ではすでにNode.jsが利用できるようになっており、それに伴い`npm`も利用できます。
プロダクション環境で`npm`を利用した`yarn`の導入は推奨されていませんが、今回はチュートリアルということで許容したいと思います。

```sh
$ npm install --global yarn
$ yarn --version
```

`yarn`のバージョンが表示されたら成功です。改めて、webpackerのインストールタスクを実行します。

```sh
$ rails webpacker:install
```

無事にタスクが終了し、設定ファイルなどの生成が成功したかと思います。

## 15.1.3 webpackerのインストールタスクで生成されたファイルの説明

`git`で差分を確認してみると、生成されたファイルを一覧できます。

```sh
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   .gitignore
        modified:   Gemfile
        modified:   Gemfile.lock
        modified:   package.json

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        .babelrc
        .postcssrc.yml
        app/javascript/
        bin/webpack
        bin/webpack-dev-server
        config/webpack/
        config/webpacker.yml
        yarn.lock

no changes added to commit (use "git add" and/or "git commit -a")
```

簡単に幾つかのファイルの説明をしたいと思います。

**package.json**

`package.json`は、`npm`のパッケージやそのバージョンが書かれたファイルです。`gem`でいう`Gemfile`のようなものです。`Gemfile`は`bundle install`を実行すると`Gemfile.lock`というファイルが作成され、これによりバージョンが固定されます。`Gemfile.lock`を他の環境に持っていくことで、他の環境でも全く同じ環境を作成することが出来ます。同等の機能が`npm`にもあり、`yarn.lock`というファルがその役割を果たします。

**.babelrc**

`.babelrc`は`Babel`というトランスパイラの設定ファイルです。JavaScriptはその言語の成長により、とても早いサイクルで言語仕様が追加されています。InternetExplorer、Safari、ChromeやFirefoxといったブラウザがこのJavaScriptを解釈するのですが、JavaScriptの新しい言語仕様への追いつき方がブラウザ毎に異なっていたり、そもそもブラウザの利用者がブラウザを最新のものにすぐにアップデートしてくれるとも限りません。このため、ブラウザが解釈できるJavaScirptに新しい言語仕様で書かれたコードを変換するのが`Babel`というトランスパイラの役割です。
`.babelrc`の中身を見てみると、どの程度のブラウザをサポートするのかという記述を発見できます。

```json

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": "> 1%",
        ...
```

`> 1%`と書かれているのは、1%以上のシェアのあるブラウザが解釈できるJavaScriptにトランスパイルしますという意味です。つまり、この設定にした場合、あなたのRailsで作成したアプリケーションの利用者の内の1%は、あなたのアプリケーションを正常に利用できない可能性があるということを覚えておいてください。他にも、`last 2 versions`や`not ie <= 8`などの書き方、それらを同時に指定するといったこともできるので、色々と設定しどのようにトランスパイル後のコードが変化するのかを試してみてください。

**app/javascript/\***

`app/javascript/`以下に`packs`というディレクトリがあり、その中に`application.js`というファイルがあると思います。これが、HTML（erb）から読み込まれるファイルになります。

**bin/\***

`bin/webpack`や`bin/webpack-dev-server`は、`webpacker`をインストールした際に作成されたコマンドで、それぞれコンパイルやコンパイル用サーバを立ち上げるコマンドになります。

**config/\***

`config/webpack/*`や`config/webpacker.yml`は`bin/webpack`などのコマンドを実行した際に利用される設定ファイルになります。基本的な設定は`config/webpacker.yml`に書いてあり、環境毎に異なる設定は`config/webpack/`以下のファイルに書きます。


## 15.1.4 webpackの利用

`app/javascript/packs/application.js`を見てみましょう。

```js:app/javascript/packs/application.js
.
.
.
console.log('Hello World from Webpacker')
```

このファイルを読み込んでブラウザのコンソールに`Hello World from Webpacker`という文字列を表示させてみましょう。
ただし、15.1.3で説明したように、このままのJavaScriptを利用することはできず、BabelでトランスパイルしたJavaScriptを読み込む必要があります。
このトランスパイルは、以下のコマンドで実行できます。

```sh
$ bin/webpack
```

これで生成されるファイルは、`application-8f8bdd9f4ff51391ec46.js`の様なダイジェスト付きのファイル名になり、`public/packs/`以下に生成されます。この出力先のディレクトリは`config/webpacker.yml`で指定されています。
同時に生成される`manifest.json`には、元のファイル名とダイジェスト付きのファイル名の対応が書かれています。


## 15.1.5 トランスパイルされたファイルの読み込み

トランスパイルが完了したら、HTML（erb）からファイルを読み込めるようになっています。
`app/views/layouts/application.html.erb`にコードを追加します。

![diff1.png](https://qiita-image-store.s3.amazonaws.com/0/20406/f2957275-a7d5-6017-d2b5-b5374e86009d.png)

サーバを再起動して、ブラウザで実際にページにアクセスして見ましょう。
デベロッパーツールなどを開いて、コンソールを立ち上げると、`Hello World from Webpacker`という文字列がコンソールに表示されています。


さて、webpackerのインストールタスクを実行した際に、`bin/webpack-dev-server`というコマンドが追加されていました。
これは何なのでしょうか？実行してみましょう。

```sh
$ bin/webpack-dev-server
.
.
.
[13] ./node_modules/loglevel/lib/loglevel.js 7.86 kB {0} [built]
[14] (webpack)-dev-server/client/socket.js 1.05 kB {0} [built]
[15] ./node_modules/sockjs-client/dist/sockjs.js 181 kB {0} [built]
[16] (webpack)-dev-server/client/overlay.js 3.73 kB {0} [built]
[18] ./node_modules/html-entities/index.js 231 bytes {0} [built]
[21] (webpack)/hot nonrecursive ^\.\/log$ 170 bytes {0} [built]
[23] (webpack)/hot/emitter.js 75 bytes {0} [built]
[25] ./app/javascript/packs/application.js 515 bytes {0} [built]
  + 11 hidden modules
webpack: Compiled successfully.
```

コンパイルが成功したというメッセージが表示されましたが、コンソールが返ってきません。
`Ctrl+C`で終了できます。

さて、もう一度`bin/webpack-dev-server`を実行し、その状態で`app/javascript/packs/application.js`を編集して見てください。ファイルが変更が保存されたタイミングで、トランスパイルが実行されたと思います。
この`webpack-dev-server`を立ち上げておくことで、ファイルの変更の度にコンソールから`$ bin/webpack`を実行しなくても自動でトランスパイルが実行されるようになります。
`webpack-dev-server`を立ち上げた状態で、再度Railsのサーバを再起動してください。
Railsの起動時に`webpack-dev-server`が立ち上がっている場合、Webpackerが自動でwebpack-dev-serverで立ち上がるサーバからアセットを取得するように切り替えてくれます。

ここで、コンソールを見るとエラーが出ていると思います。
Cloud9で`webpack-dev-server`を実行するための設定が必要でした。
`config/webpacker.yml`の`dev_server`の項目を書き換えましょう。

![diff2.png](https://qiita-image-store.s3.amazonaws.com/0/20406/187fdb9e-3645-2027-d5e0-9db72c70c00f.png)

これで、`webpack-dev-server`で自動的にコンパイルされたアセットを、ブラウザのリロードで読み込めるようになります。

※ `[WDS] Disconnected!`というエラーがコンソールに表示されると思います。5分間調べましたが解決できなかったので、無視してすすめます。コンソールに赤い文字が出てくることにより健康を害する方は、`webpack-dev-server`の利用を控えて下さい。

# 15.2 Reactの導入

## 15.2.1 Reactのインストール

ここからReact.jsの学習に入っていきます。WebpackerのタスクにReactをインストールするタスクがあるので、これを実行します。

```sh
$ rails webpacker:install:react
```

これでReactが使えるようになりました。簡単ですね！
`app/javascript/packs/hello_react.jsx`が追加されています。この状態で、`bin/webpack`を実行しましょう。

```sh
$ bin/webpack
```

`public/packs/hello_react-5612ffe4e734573493a4.js`が生成されました。

## 15.2.2 Reactコンポーネントの利用

作成されたReactコンポーネントのJavaScriptを読み込みます。
`app/views/layouts/application.html.erb`にコードを追加します。

![03.png](https://qiita-image-store.s3.amazonaws.com/0/20406/b5c65520-046c-e582-cdc2-4537037cd7d0.png)


ブラウザをリロードしてみましょう。画面の左下に`Hello React!`が表示されているはずです。

![Hello React!が表示された](https://qiita-image-store.s3.amazonaws.com/0/20406/0b254d8e-f3b1-6c6c-7175-8bd1fefc420b.png)

さて、この`Hello React!`を出力している`app/javascript/packs/hello_react.jsx`を見ていきたいと思います。

```js
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
```

これらは、`React`や`ReactDOM`といったモジュールをインポートしています。この様に記述することで、`window.React`のようにグローバルな名前空間を汚染することなく、モジュールを利用することができます。これらのモジュールがどの様に利用されるかを見ていきましょう。

```js
const Hello = props => (
  <div>Hello {props.name}!</div>
)
```

これがコンポーネントになります。少し昔の書き方をすると以下のようになります。

```js
var Hello = function(props) {
  return (
    <div>Hello {props.name}!</div>
  )
}
```

`() => {}`は新しいJavaScriptの記法で、`function() {}`の略です[^1]。慣れていきましょう。
JavaScriptの中で普通にHTMLが書けているように見えますが、これはJSXという記法です。基本的にはHTMLのように書けますが、`class="btn"`を`className="btn"`と書く必要があったりと微妙に異なります。

さらに言うと、実は上のコードはコンポーネントの省略した書き方で、冗長な書き方をすると次のようになります。

```js
class Hello extends React.Component {
  render() {
    return <div>Hello {props.name}!</div>
  }
}
```

コード上にReactというモジュールが（インポートされていたのに）利用されていませんでしたが、それは実は省略されていたからでした。利用しないのでインポートしなくても問題無い場合もありますが、JSX記法を利用する場合は（ESLintなどを導入した時にエラーが出るので）、インポートしておきましょう。
`React.Component`という記述が現れると、コンポーネントという感じが増しましたね。ただ、基本的にはこのようなシンプルなものは省略して書きましょう。

さて、省略した書き方での`function`が表すとおり、これは`props`というオブジェクトを受け取ってHTMLを返す関数になっています。Reactコンポーネントとは関数なのかと思う方もいるかもしれませんが、（機能的には）ほぼその認識で間違いありません。関数は引数に対する結果を返します。基本的には、引数が同じ場合は同じ結果が返されます。Reactコンポーネントは引数として`props`というオブジェクトを受け取り、その内容が同じであれば同じHTMLを出力します。今回のHelloコンポーネントであれば、`props.name`が同じである限り、返されるHTMLの中身は変わらないですよね。

[^1]: 正確には`function() {}.bind(this)`の略です

この`props`ですが、どういう値をコンポーネントの外から受け取るのかという指定ができます。

```js
Hello.defaultProps = {
  name: 'David'
}

Hello.propTypes = {
  name: PropTypes.string
}
```

これは、Helloコンポーネントの`props`に値がどのような型で含まれるのかが定義されています。
上のように定義すると、`props.name`は`string`型でなくてはいけません。また、`props.name`が指定されなかった場合、その値は`David`になります。
ではこの`props.name`はどこで指定するのでしょうか。その答えがコードの最後のブロックにあります。

```js
document.addEventListener('DOMContentLoaded', () => {
  ReactDOM.render(
    <Hello name="React" />,
    document.body.appendChild(document.createElement('div')),
  )
})
```

3行目に`<Hello name="React" />`とかかれていますが、このようにコンポーネントの属性値として`props`に値を渡します。
`ReactDOM.render`は2つの引数をとり、1つ目の引数がコンポーネント、2つ目の引数がそのコンポーネントが表示されるDOM要素になります。Helloコンポーネントは、bodyタグの最後に追加された空の`div`の中に表示されます。

Reactコンポーネントが`render`の結果を変化させるのは、`props`が変わったタイミング以外では`state`と呼ばれる内部状態が変化したときとなります。これは、15.3.2のカウンターコンポーネントの作成のタイミングで説明したいと思います。

## 15.2.3 Reactコンポーネントを自由に表示する

さて、Helloコンポーネントはbodyタグ内の一番最後に表示されました。これを例えばロゴの後ろやボタンの前に表示するにはどうしたらいいでしょうか。もちろん位置指定の記述を書き換えればいいのですが、ちょうど良いセレクタがない場合はどうしたらいいでしょうか。セレクタがない場合は、空の`<div/>`を用意し、それに`id`を付与する必要があります。しかしJavaScriptとHTMLのIDやクラスが密に関係しあっていて大丈夫なのでしょうか。色々と問題がありそうに思えます。このような問題を、新たに`react-rails`というgemを導入することで解決します。

`Gemfile`を以下のように変更します。

![gem.png](https://qiita-image-store.s3.amazonaws.com/0/20406/b62b11f8-1e07-ab4b-c211-dcb00bd413d4.png)

その後、おなじみの`bundle install`を実行しましょう。

```sh
$ bundle install
```

`react-rails`がインストールできたら、各種ファイルの生成などを行いましょう。以下のコマンドを実行することで、依存するライブラリがインストールされたり、`app/javascript/packs/application.js`に新しく`react-rails`の設定が追記されたりします。

```sh
$ rails generate react:install
```

さて、`react-rails`ですが利用するにあたり1つルールがあります。それが`app/javascripts/components`以下にコンポーネントを設置しなくていはいけないというルールです。このルールに従っておくと、コンポーネントの読み込みを`react-rails`が自動で行ってくれます。
Helloコンポーネントを`app/javascripts/components/hello_react.jsx`に移動しましょう。また、`ReactDOM.render()`は`react-rails`が実行してくれるのでコメントアウトして、`export default Hello`を追記しましょう。
この`export default Hello`は、このJavaScriptファイルがインポートされたときに、デフォルトで何がエクスポートされるのかということを指定しています。`hello_react.jsx`をインポートした際は、Helloコンポーネントが返されることが期待されるため、`export default Hello`のようにHelloコンポーネントをエクスポートしています。

![hello.png](https://qiita-image-store.s3.amazonaws.com/0/20406/85a31baf-e78f-388c-7c8b-56cee4ddbb5d.png)

`app/javascript/packs/application.js`に追記された`react-rails`の設定で、コンポーネントを明示的に`import`する必要はありません。`react-rails`が用意したヘルパメソッドに、`app/javascripts/components`以下に置かれたコンポーネントのファイル名を指定してあげることでインポートできます。
`app/views/layouts/application.html.erb`のコードを、`react-rails`のヘルパメソッドを利用するように変更しましょう。

![app.png](https://qiita-image-store.s3.amazonaws.com/0/20406/a4299082-f1be-6a58-197f-4aedc831d81f.png)

これで好きな場所にReactコンポーネントを表示できるようになりました。

# 15.3 カウンターコンポーネントを作る

![screencapture-rails-tutorial-kouheiszk-c9users-io-1511759097321.png](https://qiita-image-store.s3.amazonaws.com/0/20406/fce0c3f7-2c46-878e-e519-85a45475451f.png)

## 15.3.1 従来の実装

Reactを利用せずに普通に実装するなら、まずHTMLにカウント値を表示できるようにして、押すとカウントアップするボタンと、押すとカウントダウンするボタンの2つを表示できるようにします。とりあえず、`app/views/layouts/application.html.erb`にコードを追加してみましょう。

![counter_app.png](https://qiita-image-store.s3.amazonaws.com/0/20406/ed680baa-17f4-790c-8260-527d8849b462.png)

数字の0とボタンが2つ表示されるようにできました。次に、ボタンを押すと数字が変化するようにしましょう。ボタンのクリックイベントで数字が変化するようにします。

![counter_js.png](https://qiita-image-store.s3.amazonaws.com/0/20406/fa7fc4a7-6209-a4df-d08e-157301fa1058.png)

さて、この程度の簡単なプログラムであればいいのですが、例えば3の倍数のときには`fizz`と、5の倍数のときには`buzz`と、3と5の倍数の場合は`fizzbuzz`と表示したいとなったらどうしたらいいでしょうか。（やってみてください。）
また、このようにHTMLのいろいろな要素にイベントを生やす際はDOMにid属性を付与して回る必要が出てきますが、重複しないようにするために何らかの工夫が必要そうです。

## 15.3.2 Reactコンポーネントで作成する

カウンターコンポーネントを作りましょう。

![counter_component.png](https://qiita-image-store.s3.amazonaws.com/0/20406/2af08302-62d5-7d5e-774c-792a8db22aff.png)

![counter_app.png](https://qiita-image-store.s3.amazonaws.com/0/20406/8771d6c2-5e99-d058-63fb-282c5cf22f1a.png)

イベントを生やします。

![hoge.png](https://qiita-image-store.s3.amazonaws.com/0/20406/a0b77d1b-611a-b6dc-7be8-fe294b25288b.png)

ID指定などせずにイベントを直接生やせるので便利ですね！
初期値が0ですが、10から始めたりできるように、初期値を外から渡しましょう。

![props.png](https://qiita-image-store.s3.amazonaws.com/0/20406/41cd4b47-edd9-2de6-ab1c-8270d0241ac9.png)

![count.png](https://qiita-image-store.s3.amazonaws.com/0/20406/bc51d350-6552-9e22-3764-c0ba0a1a5800.png)

カウンターが10から始まっていると思います。
ボタンをクリックしたりして、正しく動いているか確認してみましょう。

ここまでできたら、いよいよ実践です。
`app/views/layouts/application.html.erb`に追加したコードの幾つかは一旦削除しましょう。

![remove.png](https://qiita-image-store.s3.amazonaws.com/0/20406/93684e56-5285-c5f4-dcc1-77630cad6775.png)

# 15.4 フォローボタンのコンポーネントを作る

## 15.4.1 フォローボタンのコンポーネント雛形作成

はじめは、特に機能を入れ込まず、外枠だけ作っていきます。
フォローボタンのコンポーネントには、誰をフォローするのか（あるいはしているのか）という情報と、もしもフォローしている場合はそのリレーション情報を渡します。コンポーネントのファイル名を`follow_button.jsx`にするとして、呼び出しは次のとおりになります。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/a6825a77-6381-fc59-6925-76738da69c12.png)

フォローボタンは、フォローの状態によってボタンのテキストの内容が変わるようにします。渡されるプロパティは、`relationship`が存在しない場合が`null`になるということを考慮して、プロパティに渡された`relationship`が`null`の場合にはフォローしていないということにします。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/9671807a-b6f4-d073-b651-8d5f4d9e482d.png)

このボタンを押すとフォロー状態が変化します。フォローしているかどうかを`relationship`の値が`null`かどうかで判断すると書きましたが、`props`の値は変化させられません。コンポーネント内で変化するものはカウンターコンポーネントのカウント値同様`state`で管理しましょう。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/1006730f-1172-8bcd-bb1e-1726fc18d80d.png)

さて、次にクリック時のイベント定義していきましょう。こちらも、フォローしているかどうかで挙動が変わるようにします。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/2cbcb4d9-9028-9a72-d95e-77697e1bd22f.png)

## 15.4.2 コンポーネントから通信する

フォローボタンが押されたらフォローを、アンフォローボタンが押されたらフォローの解除をサーバにリクエストします。
Railsチュートリアルではこれをフォームのサブミットで行って（さらにJavaScriptを利用してDOMの中身を書き換えて）いましたが、Reactはレスポンスを利用して自信の`state`を書き換えます。`state`が書き換わることによって`render()`で表示される内容が変化します。
さっそくフォローするコードを書いていきましょう。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/92b9fb7d-e2fb-f9e9-26ff-9961e0019ff5.png)

通信には、jQueryの`$.ajax`を利用しました。各パラメータの意味は

```js
$.ajax({
  type: 'POST', // HTTPのメソッド
  url: `/relationships`, // リクエスト先のURL
  dataType: 'json', // リクエストの種類
  contentType: 'application/json', // レスポンスの種類
  data: JSON.stringify({
    followed_id: this.props.user.id
  }) // 実際に送信するデータ
})
```

となっています。最後の`beforeSend`ですが、これはCSRFでないことをRailsに教えるための秘密の文字列をリクエストのヘッダに仕込むものになります。あまり覚える必要はありませんが無いと動かないので、これはRailsで必要なものだと思っていてください。

```js
beforeSend: function(xhr) {
  xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))
}
```

さて、これでフォロー状態の切り替えのリクエストを送信できるようになりましたが、Rails側がJSONでのリクエストに対応していません。
JSONでのリクエストに対応させましょう。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/2c24dc60-3b00-da6e-2787-6a7d50d42aac.png)

これで、JSONでリクエストした場合フォロー情報を作成し、作成したフォロー情報をレスポンスとしてJSONで返してくれます。
レスポンスのJSONは自動的にJavaScriptのオブジェクトとなり、`then`で取得できます。
取得した`relationship`で、`state`を書き換えます。

```js
.then((response) => {
  const relationship = response
  this.setState({
    relationship
  })
})
```

同様にフォロー解除もできるようにしましょう。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/8f51a13d-979a-8f17-f46b-7207481fd73b.png)

![sadf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/573720b4-a216-5ce9-9f35-705739c4124b.png)

## 15.4.3 チャタリングを防止する

フォローとフォローの解除ができましたが、ボタンを連打すると2回フォローリクエストやフォロー解除リクエストが走ってしまいます。リクエスト中は、ボタンの操作を行えないように、stateにリクエスト中のフラグを用意しましょう。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/53a07d41-492e-915d-eb67-5eae57b69918.png)

## 15.4.4 ボタンの見た目を良くする

ボタンの見た目ですが、無機質で味気ないですね。

![asfd.png](https://qiita-image-store.s3.amazonaws.com/0/20406/dec6536c-7c40-1215-e3f7-f6d475490ee8.png)

HTMLの属性にclassを追加して見栄えを良くしたいですが、状態によってクラスを変えようとすると次のようなコードになりがちです。

```js
let className = 'btn'
if (isFollowing) {
  className += ' btn-danger'
}
else {
  className += ' btn-primary'
}
```

あまり可読性が良くない上に、状態によるclassNameの出し分けが増えた場合、正しいクラス名を付与するのが至難の技となります。`classnames`というモジュールを導入して、状態によるクラス名の変更を簡単にしましょう。`npm`ではなく`yarn`で追加です。

```sh
$ yarn add classnames
```

これでモジュールを利用できるようになりました。コードを以下のように変更します。

![asfd.png](https://qiita-image-store.s3.amazonaws.com/0/20406/c54768ab-caa5-c5fc-808e-ce5ef92ed0db.png)

あとは、以前のフォローボタンを削除すれば完成です！

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/52999809-866a-902b-e4ce-ab293c40e996.png)

見た目も良いフォロー/フォロー解除ボタンが完成しました。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/92d9b6f8-19e2-3bf7-ae31-e52e8b75b3aa.png)

# 15.5 Reduxの紹介

## 15.5.1 Reduxとは

Reactはそれだけでも利用するメリットが大きいですが、Reduxと一緒に利用されることも多いと思います。
Reactは使えるが、その上でReduxを使うとなると一気にハードルが上がるという人も多いのでは無いでしょうか。ということで、このチュートリアルではReduxの使い方も併せて簡単に説明したいと思います。
Reduxは、Reactのコンポーネント内での`state`の管理を1つにまとめ、Fluxというデータフローの考え方でその`state`を管理するためのライブラリです。

詳しい説明は、公式が分かりやすいと思います。公式のBasicsを読むとReduxをおおまかに理解できると思います。
https://redux.js.org/docs/basics/

Reduxは以下に示す登場人物が多いことも理解が進みにくい原因の一つだと思います。ただし、良く見てみるとそれぞれの役割は単純で、またデータの流れる方向も以下のリストの順番通りです。これを踏まえて、Reactで作成したカウンターコンポーネントをReduxで書き直してみましょう。

| 登場人物 | 役割 |
|:---|:---|
| Action | アクション |
| ActionCreator | アクションを発生させる |
| Reducer | アクションを受け取って、Storeのステートを更新する |
| Store | ステートを管理する |
| ContainerComponent | Reactのコンポーネントに、StoreのステートやActionCreatorを渡す |
| Component | Reactコンポーネント |

## 15.5.2 準備

Reduxを始める前に、まずは空のコンポーネントを新しく作りましょう。ファイルの配置も少し変え`app/javascript/counter/index.js`を以下の内容で作成してください。

<img width="1005" alt="スクリーンショット 2017-11-28 1.24.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/20406/1b71f146-db8f-db71-bcee-8812ec40c552.png">

ただ空の`<div />`を表示するだけのコンポーネントになっています。
このコンポーネントを`react-rails`で読み込めるようにします。`react-rails`は`app/javascripts/components`以下に配置されたファイル名以外に、`window.*`に公開されているコンポーネントも読み込んでくれます。先程作成したコンポーネントを`windows.Counter`で読めるようにしましょう。
`app/javascript/packs/application.js`を編集します。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/24aa9b7a-14e7-a5e1-1428-8a4088adfc18.png)

さて、これで`<%= react_componennt('Counter') %>`とRailsのViewで読み込めるようになりました。
`app/views/layouts/application.html.erb`で読み込みましょう。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/f90b5612-b713-f1a9-4b34-d91e247a1eb3.png)

最後に、プロジェクトでReduxを利用できるようにしましょう。`redux`と`react-redux`というNodeモジュールを追加します。

```sh
$ yarn add redux react-redux
```

以上で準備は完了です。早速Reduxでカウンターを作成していきましょう。

## 15.5.3 Reduxでカウンターを作成する

### Actioin

Reduxは`Action`と呼ばれるデータをやり取りすることで`state`を変化させます。
`Action`はJavaScriptのオブジェクトで、以下のような構造です。

```js
{
  type: "CLICK",
  button: "a-button"
}
```

```js
{
  type: "SIGN_IN",
  user: {
    id: 12345,
    name: "David"
  }
}
```

`Action`は必ず`type`を持っているほかは特に形式に決まりはありません。`type`だけを持つ`Action`でも大丈夫です。
カウンターアプリで考えられるアクションは、カウントアップとカウントダウンの2つです。この`Action`を作っていきましょう。
ここで、Reduxでは`Action`の`type`はReduxアプリケーションの中で一意である必要があります。重複などを避けるために、定数として管理しましょう。

![asdf.png](https://qiita-image-store.s3.amazonaws.com/0/20406/c4807ae7-b6ba-6700-1290-e29262c47256.png)

この定数を利用して、`Action`を定義していきます。

### ActionCreator

`Action`は`ActionCreator`によって作成されます。
`ActionCreator`は関数の形をとり、実行されると`Action`を返します。

<img width="984" alt="スクリーンショット 2017-11-28 11.03.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/20406/48ae4540-8a7f-c145-0af1-7fd8c50d9cb4.png">

### Reducer

`ActionCreator`が作成した`Action`は`Reducer`に伝わり、`Reducer`は`Action`を受け取り、その内容を元に`Store`を更新します。カウンターでは、カウントアップの`Action`を受けっ取ったら`Store`にあるカウント値をインクリメントし、カウントダウンの`Action`を受け取ったら`Store`にあるカウント値をデクリメントします。

<img width="987" alt="スクリーンショット 2017-11-28 11.07.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/20406/eeb2c6c7-722f-89b8-74b9-5489e4fbd8d3.png">

`Reducer`は`Store`の初期値も決定できます。Reduxの初期化の際に特別な`Action`が発行され、それが`Reducer`に伝わることで`state`のデフォルト値が`Store`に入ります。

`Reducer`は往々にして肥大化することが多く、予め複数のファイルに分けておくことが推奨されます。
複数に分割された`Reducer`をまとめる`combineReducer`というメソッドが`redux`から提供されています。

<img width="989" alt="スクリーンショット 2017-11-28 11.15.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/20406/33116685-636a-5660-aac5-65525bfe3365.png">

### Store

`Store`はステートを管理する以外に特に説明することはありません。
`redux`はミドルウェアと呼ばれる幾つかの拡張が用意されていて、`Store`の中身を見ることのできるミドルウェアも存在します。そういったミドルウェアを利用する場合はこのファイルに幾つか記載します。

<img width="991" alt="スクリーンショット 2017-11-28 11.17.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/20406/478b9321-b616-f579-f817-5b3ebfa363b0.png">

さて、`Store`まで完成したので、コンポーネントとのつなぎこみを行って行きましょう。

### ContainerComponent

`ContainerComponent`はReactコンポーネントに、`Store`の`state`や`ActionCreator`[^2]を渡す役割を担います。
`state`や`ActionCreator`はコンポーネントに`props`として渡ります。コンポーネントにどの`state`や`ActionCreator`をどういった名前で渡してやるのかというマッピングを行うのが`ContainerComponent`の役割になります。

<img width="986" alt="スクリーンショット 2017-11-28 11.27.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/20406/f9d8e034-c000-bf5c-eee0-508320b03932.png">

`mapStateToProps`に渡ってくる`state`は`reducer`毎に別れています。今回は`count`という`reducer`を作りましたので、`state.count`の`count`がコンポーネントに渡すカウント値になります。

`mapDispatchToProps`では、コンポーネントに渡す関数を用意します。コンポーネントが渡された関数を実行すると、対応する`ActionCreator`が実行され、`Action`が発行されます。

[^2]: 正確には`ActionCreator`のDispacherがコンポーネントに渡されます

### Component

コンポーネントが自身の状態を`state`に持つ時代は終わりを迎えました。
今ではコンポーネントは、外部から渡される`props`によってコンポーネントの`render()`する結果が決定されるようになりました。

<img width="986" alt="スクリーンショット 2017-11-28 11.33.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/20406/54456122-e489-8d90-19c0-cf65e28d96f2.png">

## 15.5.4 Storeとコンポーネントを接続する

実はあと数ステップReduxを動かすために必要な動作が残っています。
`ContainerComponent`が`Component`を参照したので、あとは`ContainerComponent`に`Store`を渡せばいいのでは。そう思われる人も多いと思います。実は、`Store`を渡すのは`ContainerComponent`ではなく普通の`Component`がいいとされているため、今度は`ContainerComponent`を呼び出す`Component`を作成します。
このコンポーネントの名前はどのように名付けても良いのですが、ここでは`App`としています。

<img width="986" alt="スクリーンショット 2017-11-28 11.38.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/20406/6695204d-d5aa-6144-55c8-90d7be49ff53.png">

`import Counter from '../containers/counter'`で`ContainerComponent`をインポートしていることに注意してください。
これであとは、`Store`をこのコンポーネントに渡せばカウンターが動作するようになります。一番はじめに作成した`app/javascript/counter/index.js`がこのコンポーネントを読み込むようにし、さらには`Store`が渡されるようにしましょう。

<img width="986" alt="スクリーンショット 2017-11-28 11.41.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/20406/9de16394-823e-91bb-41ff-0db54b816749.png">

`react-redux`が`Provider`というコンポーネントを用意してくれており、このコンポーネントの中に`App`コンポーネントを記述します。`Provider`は`store`を属性値として受け取ることができ、受け取った`store`は`Provider`の中に存在するコンポーネントが読み込んでいる`ContainerComponent`に渡されます。

トランスパイルが成功していることを確認しブラウザをリロードすると、カウンターが表示されるようになりました。

![screencapture-rails-tutorial-kouheiszk-c9users-io-1511837185412.png](https://qiita-image-store.s3.amazonaws.com/0/20406/d24d5862-72b0-ceeb-1f28-f8ea7e149e7f.png)

# 15.6 最後に

## 15.6.1 本章のまとめ

- `webpacker`gemを利用することで、簡単にReactを利用した開発を行える
- JavaScriptは進化し続けていて、最新の記法で記述したJavaScriptをブラウザが理解できる形式に変換するためにトランスパイラを利用する
― JavaScriptの最新の記法は謎めいているが、慣れると非常に協力である
- Reactを利用すると、HTMLとJSをコンポーネントという単位でまとめるためて記述することができる
- Reactは渡される`props`の変化や内部の`state`が更新される度に`render`を呼び出し、`props`や`state`に応じたHTMLを返す
- JavaScriptからAjax通信を行う場合は、CSRFトークンをリクエストに含める必要がある
- ReactコンポーネントのHTMLのクラスを条件によって変えたい場合は、`classnames`を利用する
- Reduxは登場人物（ファイル）が多いだけで、各登場人物の役割は単純である
- Reduxを利用すると`state`がStoreで一元管理されるので、状態の見通しが良くなる
- Reduxは必ず利用する必要はなく、コンポーネントが複雑な`state`を持ち、それらが他のコンポーネントにも影響を与える場合に利用を検討すべき

# 演習

## 演習1

`Babel`でサポートするブラウザの指定を変更し、全てのブラウザの最新バージョン（IEはIE11とMS　edge）をサポートするような指定にしてみましょう。また、指定の違いで出力がどう異なるかも可能であれば調べましょう。

## 演習2

コンポーネントを表示する際に渡すHashを用意する際に、`user.attributes`を利用するとHashで表現したくないプロパティもHashになってしまったと思います。
例えばユーザを表示するコンポーネントの場合、管理権限を持っているかどうか（管理権限を管理するカラムがユーザテーブルに存在する）ということが分かってしまいます。
パスワードの項目や管理者権限を持っているかどうかが`user.attributes`することで利用者に見える状態になってしまうのは良くないので、これを防ぐ方法を考えましょう。

また、例えばマイクロポストコンポーネントがあったときに、

```ruby
render_component('micropost', micropost: micropost.attributes)
```

としたいですが、この中で`micropost.user.name`などは取得できません。これを取得する方法も同時に考えましょう。

Hint: 例えば`representer`というgemを利用します。

## 演習3

ユーザ情報を表示するコンポーネントを作成して、マイクロポストのユーザ名をマウスオーバーするとツールチップ形状のモーダルでそのコンポーネントが表示されるようにしてみてください。

Hint: https://github.com/react-bootstrap/react-overlays　の利用を検討してみてください

## 演習4

マイクロポストの一覧を表示するコンポーネントを作成してください。
もちろん演習3の内容は引き継ぎます。

Hint: マイクロポストの削除は、コンポーネント内でリクエストするようにしましょう。ページ遷移が発生せずにコンポーネントが削除されるにはどうしたら良いか考えましょう。

## 演習5

マイクロポストをTwitterの様にモーダルで表示できるようにします。
マイクロポストコンポーネント（あるいはマイクロポストコンポーネント内に設置したボタン）をクリックすると、そのモーダルが表示されるようにしましょう。
モーダルが表示されたタイミングでURLを動的に書き換え、パラメータにマイクロポストを表すIDを含めます。
マイクロポストを表すIDが含まれたURLにアクセスすると、該当するツイートがモーダルで表示されるようにもしてみてください。

## 演習6

Reduxで作成したカウンターに初期値を外から与える方法を考えましょう。

## 演習7

演習4や演習5で作成したマイクロポストを表示するコンポーネントを、Reduxで書き換えてみましょう。