React を、サンプルを実装しながら理解する

![ReactFormApp.gif](https://qiita-image-store.s3.amazonaws.com/0/118759/83ce7234-52ce-897e-6b3f-2fcb94f21b16.gif)

画像のようなサンプルアプリを実装してみます。

[公式のチュートリアル](https://reactjs.org/tutorial/tutorial.html) でも同じように学習できますが、この記事は

- より単純で分量が少ない
- 日本語で書かれている
- 同じアプリを Angular でも実装している
- Redux 導入の前段階にできる（Redux の記事は後日作成予定）

これらの点が異なっています。[^異なっている点]

[^異なっている点]: 私も React 初学者なので、当然品質も異なります。みなさまのフィードバックをお待ちしています。



# サンプル実装の手順

## まずは Hello world

[Create React App](https://github.com/facebookincubator/create-react-app) を使い、まずは環境をセットアップします。
Create React App を npm （Yarn や npx など任意のツールで構いません）でインストールし、新規プロジェクトを作成、開発サーバーを起動します：

```bash
yarn global add create-react-app
create-react-app qiita
cd qiita
yarn start
```

App.js の中身を以下のように、必要最低限に書き換えます：

```js:src/App.js
import React, { Component } from 'react';

class App extends Component {
  render() {
    return (
      <h1>Hello world</h1>
    );
  }
}

export default App;
```

ブラウザーがリロードされ、画面に Hello world が表示されたら成功です。

### CodePen で環境設定をスキップする

[公式チュートリアルの CodePen](https://codepen.io/gaearon/pen/oWWQNa?editors=0010) を流用し、ブラウザー上で試すこともできます。
その際は、以下のような違いに気をつけます：

- import/export 文なしにする
- Component クラスは `React.Component` として参照する
- `ReactDOM.render()` のおまじないを書く
- ファイル分けはできないので、一か所に全クラスを書く

コードはこのようになります：

```js
class App extends React.Component {
  render() {
    return (
      <h1>Hello world</h1>
    );
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
```


## フォームの見た目だけ作成する

`<h1>Hello world</h1>` の部分をインプット要素に置き換えます：

```js:src/App.js（抜粋）
class App extends Component {
  render() {
    return (
      <div>
        <input type="text" />
        <button>SEND</button>
      </div>
    );
  }
}
```

テキストボックスとボタンが表示されるのを確認したら、これをコンポーネント化し、別のファイルに切り出します[^別ファイルにする理由]：

[^別ファイルにする理由]: 同じファイルに書いても動作しますが、コンポーネントを再利用することを考え、別ファイルにしています。

```js:src/App.js
import React, { Component } from 'react';
import { FormApp } from './FormApp';

class App extends Component {
  render() {
    return (
      <FormApp />
    );
  }
}

export default App;
```

```js:src/FormApp.js
import React, { Component } from 'react';

export class FormApp extends Component {
  render() {
    return (
      <div>
        <input type="text" />
        <button>SEND</button>
      </div>
    );
  }
}
```

コンポーネントは、Component クラスを拡張したクラスです。
`<FormApp />` のように呼び出すと、`render()` メソッドが実行され、その戻り値が描画されます。

ブラウザーがリロードされ、同じ見た目となれば成功です。


## フォームにイベント検知機能を追加する

まずは、ボタンを押されたことを検知し、コンソールに文字を出力してみます：

```js:src/FormApp.js（抜粋）
export class FormApp extends Component {
  send() {
    console.log('send called!');
  }

  render() {
    return (
      <div>
        <input type="text" />
        <button onClick={this.send.bind(this)}>SEND</button>
      </div>
    );
  }
}
```

`send()` メソッドを button 要素の onClick に割り当てています。
`{}` の中身は JavaScript の式として扱われるので、Angular のように `onClick={this.send()}` としてしまうと、onClick には `send()` の戻り値 undefined が割り当てられます。
それでは意味がないので、そうしないよう気をつけます。
また `onClick={this.send}` としてしまうと、今は問題ありませんが、今後 `send()` 内で `this` を使ったときにエラーになる（`this` が undefined となる）ため、`bind()` メソッドで `this` を固定しています。

同様に、input 要素にも onChange ハンドラーを割り当てます：

```js:src/FormApp.js（抜粋）
export class FormApp extends Component {
  handleInput() {
    console.log('handleInput called!');
  }

  send() {
    console.log('send called!');
  }

  render() {
    return (
      <div>
        <input type="text" onChange={this.handleInput.bind(this)} />
        <button onClick={this.send.bind(this)}>SEND</button>
      </div>
    );
  }
}
```

## 状態 (state) を持たせ、状態を更新する

constructor を追加し、その中で特殊なプロパティ state を設定します：

```js:src/FormApp.js（抜粋）
export class FormApp extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: '',
      message: ''
    };
  }
```

constructor の引数 `props` と `super(props)` の呼び出しは、今はおまじないだと思ってください。
重要なのは `this.state = {...}` のほうで、ここで `value` と `message` という状態を初期化しています。

`value` はテキストボックスの中身、`message` はボタンを押すと表示されるメッセージを表すために使います：

```js:src/FormApp.js（抜粋）
  render() {
    return (
      <div>
        <input type="text" value={this.state.value} onChange={this.handleInput.bind(this)} />
        <button onClick={this.send.bind(this)}>SEND</button>
        <div>{this.state.message}</div>
      </div>
    );
  }
```

input 要素には `value={this.state.value}` を追加し、`message` は `<div>{this.state.message}</div>` という形でボタンの次に追加しました。
初期値を変えると、その値が画面に表示されることが確認できます。

この段階では、テキストボックスに入力することができませんが、それは正常な動きです。
次のように `handleInput()` を修正することで、テキストボックスへの入力が有効になります：

```js:src/FormApp.js（抜粋）
  handleInput({ target: { value } }) {
    this.setState({
      value
    });
  }
```

ES6 の文法は見慣れないかもしれませんが、以下のように書いたのと同じです：

```js:src/FormApp.js（抜粋）
  handleInput(event) {
    let value = event.target.value;
    this.setState({
      value: value
    });
  }
```

React には厳格なルールがあり、その一つがこれです。
`setState()` メソッド以外に state を変える方法がないという点です。
たとえユーザーの入力であっても、input 要素の value が `value={this.state.value}` となっている限り、テキストボックスの中身は `this.state.value` のままで不変です。
Angular は value の変化を自動で `this.state.value` に反映し、state の中身を変えてくれますが (two-way binding)、React は onChange イベントを呼び出すだけです。
わざわざ `setState()` を呼び出すのは無駄に見えますが、明示的に書かせることで、コードを読んでデータの流れがすぐわかるという利点を得られるのです。

この FormApp 程度ではその恩恵はありませんが、そういうものだと思って進んでください。
テキストボックスに入力が可能で、ボタンを押すとコンソールにログが出る状態のアプリができていれば、成功です。


## ボタンの機能を追加する

最後の機能を追加して、アプリを完成させます。
`send()` を以下のように修正します：

```js:src/FormApp.js（抜粋）
  send() {
    const { value } = this.state;
    this.setState({
      value: '',
      message: value
    });
  }
```

`value`、すなわちテキストボックスを空にし、div の中身に値を移しています。
ボタンを押すとテキストボックスが空になり、そのすぐ下に中身が移るアプリができていれば、成功です。


## Refactoring: チュートリアルどおりのコードスタイルに近づける

`constructor()` と `render()` の中身を修正します：

```js:src/FormApp.js（抜粋）
export class FormApp extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: '',
      message: ''
    };

    this.handleInput = this.handleInput.bind(this);
    this.send = this.send.bind(this);
  }

  // ...

  render() {
    return (
      <div>
        <input type="text" value={this.state.value} onChange={this.handleInput} />
        <button onClick={this.send}>SEND</button>
        <div>{this.state.message}</div>
      </div>
    );
  }
}
```

`render()` が呼ばれるたびに `bind()` メソッドを呼ぶのではなく、constructor 内で呼ぶようにして計算コストを下げました。
この規模のアプリでわざわざそうしたのは、性能を気にしたからではなく、constructor の中身に定型句を揃えたかったからです。
`super(props)`、state の初期化そしてメソッドの bind という 3 つのおまじないです。


## 最終的なコード

```js:src/App.js
import React, { Component } from 'react';
import { FormApp } from './FormApp';

class App extends Component {
  render() {
    return (
      <FormApp />
    );
  }
}

export default App;
```

```js:src/FormApp.js
import React, { Component } from 'react';

export class FormApp extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: '',
      message: ''
    };

    this.handleInput = this.handleInput.bind(this);
    this.send = this.send.bind(this);
  }

  handleInput({ target: { value } }) {
    this.setState({
      value
    });
  }

  send() {
    const { value } = this.state;
    this.setState({
      value: '',
      message: value
    });
  }

  render() {
    return (
      <div>
        <input type="text" value={this.state.value} onChange={this.handleInput} />
        <button onClick={this.send}>SEND</button>
        <div>{this.state.message}</div>
      </div>
    );
  }
}
```

以上でおしまいです。
おつかれさまでした。



# Angular で実装した場合

参考として、[Angular](https://angular.io) による実装を載せておきます：

```ts:src/app/form-app/form-app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-form-app',
  template: `
    <div>
      <input type="text" [(ngModel)]="value">
      <button (click)="send()">SEND</button>
      <div>{{message}}</div>
    </div>
  `
})
export class FormAppComponent {

  value = '';
  message = '';

  send() {
    const value = this.value;

    this.value = '';
    this.message = value;
  }
}
```



# 参考文献

- [Reduxの実装とReactとの連携を超シンプルなサンプルを使って解説 | maesblog](https://mae.chab.in/archives/2885)