React-Redux超入門(2)

### 概要
[前回](https://qiita.com/hiro4669/items/a181f548edc78a90b453)の記事をベースに，コードを整理すること，またコンポーネント間で値を受け渡すいくつかのやり方をまとめる．ここでは，

- テキストフィールド1つ
- 登録ボタン1つ
- 登録一覧

の要素を備え，テキストフィールドに文字を入力し，登録ボタンを押すと登録一覧が更新されるアプリケーションを例題にする．
今回は1ファイルではなく，複数ファイルでアプリケーションを構成する．


### 方針1
方針1でのコンポーネントとファイルの関係は以下の通り．

+ App.js: アプリケーションのメインファイル．Appコンポーネントを含む
+ Input.js: テキストフィールドコンポーネントを含む
+ Item.js: 追加される要素1つを表す

```jsx:Item.js
import React from 'react'

const Item = (props) => {
    return (
        <li>{props.name}</li>
    )
}
export default Item;
```
Itemは関数コンポーネントで定義する．表示する内容はProps経由で受け取る．

```jsx:Input.js
import React, {Component} from 'react'

class Input extends Component {
    render() {
        return <input type="text" ref="ff" defaultValue="Input Todo"/>
    }

}
export default Input;
```
Inputコンポーネントはクラスコンポーネントで定義する．方針1では，親コンポーネント(App)からInputコンポーネントに入力された値を参照する．そのため，`ref(s)`を使う必要があるが，関数コンポーネントにはこの`ref(s)`は使えないためである．

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

import Item from './Item';              // (1) コンポーネントのインポート
import Input from './Input';

class App extends Component {
  constructor(props) {
      super(props);
      this.state = {
          items: []
      }
      this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
      const {items} = this.state;
      items.push(this.refs.iform.refs.ff.value);  // (2)refsを使用した子(孫)コンポーネントの参照
      this.setState({items});
  }

  render() {
    return (
        <div>
          <h1>Todo App</h1>
          <Input ref="iform"/>
          <button onClick={this.handleClick}>Add</button>  // (3) ボタンとイベントハンドラの設定
          <ul>
            {
                (() => {
                    let list = [];
                    const { items } = this.state;
                    for (var i = 0; i < items.length; ++i) {
                        list.push(<Item key = {i} name={items[i]} />);
                    }
                    console.log(list);
                    return list;
                })()
            }
          </ul>
        </div>
    );
  }
}

export default App;
```

Appは`create-react-app`で生成されるものを修正して作成した．ポイントは3つ．その他は前回説明ずみ．

(1)
自作コンポーネントのインポート．ここでは同じディレクトリにインポートするファイルが存在することを仮定している．

(2)
イベントハンドラでは，ステートで管理されている`items`を取り出し，Inputコンポーネントに入力された文字列を追加してステートを更新する．方針1では，コンポーネントの値をDOMを経由して取得している．Reactは仮想DOMという単位でコンポーネントを管理しているらしく，ブラウザが管理している生のDOMを参照するには，`ref(s)`という仕組みを使う必要があるらしい．そこで，参照されるオブジェクトを一意に定めるため，`ref`を使って名前をつけている．この例では，`Input`コンポーネントに`ref="iform"`で名前をつけ，さらに`Input`コンポーネントが内包する`input`コンポーネントにも`ref="ff"`という名前をつけている．そのため，Appから`ff`のvalueのアクセスするには，

```jsx:
this.refs.iform.refs.ff.value
```
と書く必要がある．

(3)
方針1では，ボタンは`<button>`をそのまま利用し，`onClick`で(2)のイベントハンドラを関連付けている．

このアプリケーションを実行すると，Addボタンを押すたびにリストに追加されていく．

このアプリケーションは動作が，この方針1はよろしくない．なぜならば，**Appが孫コンポーネントまで知る必要がある**からである．もし，Inputコンポーネントが内包する`input`の名前が変わったり，使うコンポーネントが変わったりしたらその都度Appも変更しなければいけない．そのため，**Addボタンが押されたら，Inputから通知を受け取り，その引数で登録する値を取得したい**ものである．

### 方針2
方針2でのコンポーネントとファイルの関係は以下の通り．

+ App.js: アプリケーションのメインファイル．Appコンポーネントを含む．Input2を利用するため方針1から修正．
+ Input2.js: テキストフィールドとボタンコンポーネントを含む．
+ Item.js: 追加される要素1つを表す．方針1と変更なし

```jsx:Input2.js
import React, {Component} from 'react'

class Input2 extends Component {
    constructor(props) {
        super(props);
        this.addItem = this.addItem.bind(this);
    }

    addItem() {
        this.props.addItem(this.refs.ff.value); //(1) onClickで呼ばれ，親から渡された関数を実行する．
    }

    render() {
        return (
            <div>
                <input type="text" ref="ff" defaultValue="Input Todo"/>
                <button onClick={this.addItem}>Add</button>
            </div>
        )
    }
}
export default Input2;
```

(1)
`Input2`は，`input`と`button`を内包し，`button`の`onClick`イベントで`input`に入力された文字列を取得して`props`で渡された関数(`addItem`)を実行する.

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

import Item from './Item';
import Input from './Input';
import Input2 from './Input2';

class App extends Component {
  constructor(props) {
      super(props);
      this.state = {
          items: []
      }
      this.handleClick = this.handleClick.bind(this);
      this.addItem = this.addItem.bind(this);
  }

  handleClick() {
      const {items} = this.state;
      items.push(this.refs.iform.refs.ff.value);
      this.setState({items});

      console.log(this.refs.iform.refs.ff.value);
  }

  addItem(v) {
      const {items} = this.state;
      items.push(v);
      this.setState({items});
  }

  render() {
    return (
        <div>
          <h1>Todo App</h1>
          {
//          <Input ref="iform"/>                              (1)Input, buttonを削除
//          <button onClick={this.handleClick}>Add</button> 
          }
          <Input2 addItem={this.addItem}/>   (2) Input2を追加
          <ul>
            {
                (() => {
                    let list = [];
                    const { items } = this.state;
                    for (var i = 0; i < items.length; ++i) {
                        list.push(<Item key = {i} name={items[i]} />);
                    }
                    console.log(list);
                    return list;
                })()
            }
          </ul>
        </div>
    );
  }
}

export default App;
```

変更点は(1), (2)の2箇所．

(1)
Input2のために不要になる`Input`, `button`の削除

(2)
`Input2`を追加．Propsで`App`の関数を"addItem"という名前で渡している．こうすることで，`Input2`ではこの関数をProps経由で実行できるようになる．

方針2では，方針1でやりたかったことが実現できている．すなわち，コールバックを作成(`this.addItem`)し，下位コンポーネントに登録することで，下位からの通知を受け取る．この時，下位コンポーネントからは上位コンポーネントが必要な情報を引数で受け取っている．
方針2でもまあ，モジュール分割がうまくできるとは言える．でも，どうやらReactでは`ref(s)`でHTMLのDOMにアクセスするのはできるだけ避けたほうが望ましいらしい．

### 方針3
方針2を踏まえ，`ref(s)`を使わないように変更する．具体的には，ステートを使って値の取得を行う．

+ ItemList.js: Itemをリストで返すコンポーネント．Appのループ処理をコンポーネント化する
+ App.js: アプリケーションのメインファイル．Appコンポーネントを含む．ItemList, Input3を利用するため方針2から軽微な修正．
+ Input3.js: テキストフィールドとボタンコンポーネントを含み，ステートで入力データを管理する
+ Item.js: 追加される要素1つを表す．方針1，2と変更なし

```jsx:Input3.js
import React, {Component} from 'react';

class Input3 extends Component {
    constructor(props) {
        super(props);
        this.state = {                   // (1) ステートの設定
            value: "input something",
        }
        this.addItem = this.addItem.bind(this);
        this.handleChange = this.handleChange.bind(this);
    }

    addItem() {
        this.props.addItem(this.state.value); // (2) ボタンのイベントハンドラ
    }

    handleChange(e) {
        this.setState({...this.state, value:e.target.value}); //(3) テキストボックスのイベントハンドラ
    }

    render() {
        return (
            <div>
                <input type="text" onChange={this.handleChange} value={this.state.value}/> (4)
                <button onClick={this.addItem}>Add</button>
            </div>
        )
    }
}

export default Input3;
```
要点な4つ．
(1)
後述するテキストボックスの値を管理するためのステートを生成/初期化．

(2)
ボタンのイベントハンドラ．テキストボックスの値をステートで管理するので，`this.state.value`から値を取り出して親の`addItem`に与えている．

(3)
テキストボックスのイベントハンドラ．テキストボックスに変化がある度(e.g, 文字の入力など)に実行される．ここでは引数`e`から値を取り出して`this.state.value`に設定する．

(4)
テキストボックスの生成．`onChange`イベントにイベントハンドラを設定し，同時に`value`にステートで管理する値(`this.state.value`)をバインドしている．なお，テキストボックスでは，`value`を設定する時には同時に`onChange`イベントハンドラも設定しないと警告が出る．

```jsx:ItemList.js
import React from 'react';
import Item from './Item';

const ItemList = ({items}) => {
    let list = [];
    for (var i = 0; i < items.length; ++i) {
        list.push(<Item key = {i} name={items[i]} />);
    }
    return list;
}
export default ItemList;
```
`App`で行っていたループ処理の代わりの関数コンポーネント．

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

import Input3 from './Input3';
import ItemList from './ItemList';

class App extends Component {
  constructor(props) {
      super(props);
      this.state = {
          items: []
      }
      this.handleClick = this.handleClick.bind(this);
      this.addItem = this.addItem.bind(this);
  }

  handleClick() {
      const {items} = this.state;
      items.push(this.refs.iform.refs.ff.value);
      this.setState({items});

      console.log(this.refs.iform.refs.ff.value);
  }

  addItem(v) {
      const {items} = this.state;
      items.push(v);
      this.setState({items});
  }

  render() {
    return (
        <div>
          <h1>Todo App</h1>
            <Input3 addItem={this.addItem}/>
          <ul>
            <ItemList items={this.state.items} />            
          </ul>
        </div>
    );
  }
}

export default App;
```

単に`ItemList`と`Input3`を追加し，`Input2`をコメントアウト．

以上，どの方法でも動作するが，React的には方針3が良いのだと思われる．