今からはじめるReact.js〜propsとstate、それからrefs〜

前回→[今からはじめるReact.js〜React ver0.14〜](http://qiita.com/kuniken/items/2fc5b782da5d302247ab)

# propsとstateの特徴

なんらかの一覧表コンポーネントを作る場合、コンポーネントに表示させたいものを指定する必要があるわけですが、React.jsではデータを管理するための２つの変数が用意されています。

 - props
 - state

です。表示させたいものをこれら２つの変数に指定していきます。

２つの変数それぞれの特徴は、

||値を指定するタイミング|値の変更可否|
|:---:|:---:|:---:|
|props|コンポーネント作成時|NO|
|state|コンポーネント作成後|YES|

になります。

### ※お断り(Deprecatedについて)
本記事で取り上げている、React.createClass()とReact.PropTypesはバージョン15.5でDeprecatedになりました。
そのため、それぞれに関する記述部分は、以下を例に読み替えてください。

#### React.createClass()
```
var Component = React.createClass({
                ~~~~~~~~~~~~~~~~~←この部分はDeprecatedになりましたので、使わないことをお勧めします。
```
その代わり、以下のように記述します。
↓

```
var createReactClass = require('create-react-class');
var Component = createReactClass({
```

#### React.PropTypes
```
var Component = createReactClass({
  propTypes:{
    propsKey1: React.PropTypes.string.isRequired,//型：String、指定必須
    propsKey2: React.PropTypes.number            //型：number
               ~~~~~~~~~~~~~~~←この部分はDeprecatedになりましたので、使わないことをお勧めします。
  },
```
その代わり、以下のように記述します。
↓

```
var PropTypes = require('prop-types');
var Component = createReactClass({
  propTypes:{
    propsKey1: PropTypes.string.isRequired,//型：String、指定必須
    propsKey2: PropTypes.number            //型：number
  },

```
インストールをお忘れなく。。

```
$ npm install create-react-class prop-types --save-dev
```

### 値の指定方法
#### props
`<Component propsKey1="value1" propsKey="value2">`
のようにコンポーネント作成時に指定します。
#### state

```js
var Component = React.createClass({
  componentDidMount:function(){
    this.setState({stateKey1: "value1",
                   stateKey2: "value2"});
  },
```

のようにコンポーネント作成後、任意のタイミングで指定します。
(componentDidMountというファンクションはReact.jsが用意したファンクションで、コンポーネント作成後に実行されるようになっています)
stateに値を設定する場合は、setStateを使うようにします。
`this.state.stateKey1 = "value1"`
という書き方は認められていないようです。

### 値の取得
セットされた値を取得する場合は、単純です。

```js
render:function(){
  return (
    <div>
      <span>title:{this.props.propsKey1}</span>
      <img src={this.state.stateKey1}/>
    </div>
  );
}

```
this.props.~
this.state.~
と書くだけです。

### 初期値の指定
propsとstateで定義した変数には必ずしも値が指定されない場合もあるかもしれません。
値が指定されていなくてもサービスとしては問題なく後続処理ができるようにしなければなりません。

以下のようにします。

#### props

```js
var Component = React.createClass({
  getDefaultProps: function() {
    return {
      propsKey1: 'default value'
    };
  },
```
#### state

```js
var Component = React.createClass({
  getInitialState: function() {
    return {
      stateKey1: 'initial value'
    };
  },
```
stateの場合はファンクション名の通り、初期値を設定するイメージですね。

### propsの型・必須指定
propsはコンポーネント外部から指定されるため、おかしな値が渡されること、そもそも必須なのに値が渡されないかもしれないことを考慮したほうが良いかもしれません。

propTypesで定義することで型・必須指定を明確化できます。

```js
var Component = React.createClass({
  propTypes:{
    propsKey1: React.PropTypes.string.isRequired,//型：String、指定必須
    propsKey2: React.PropTypes.number            //型：number
  },

```
詳細は、開発元サイトが詳しいです。

~~https://facebook.github.io/react/docs/reusable-components.html~~

[https://facebook.github.io/react/docs/react-without-es6.html](https://facebook.github.io/react/docs/react-without-es6.html)

不正な値を渡したり、指定必須なのに値を渡していなかったりすると、以下のように、デベロッパーツールのコンソールにエラーが表示されます。
![スクリーンショット 2015-10-12 19.06.10.png](https://qiita-image-store.s3.amazonaws.com/0/89819/6ccfed8f-73b8-196a-a4cb-2da172e4fdc5.png)

# フォームとリストを作成してみる
以上を踏まえて、ボディにフォームとリストを表示できるようにしてみます。

仕様としては、フォームで入力したデータを追加ボタンをクリックすることでリストに追記していく、というシンプルなものです。

body.jsxを書き換えていきます。

```js:body.jsx
var React = require('react');
var ReactDOM = require('react-dom');

//ボディの定義
var Body = React.createClass({
  render: function(){
    return (
      <UserBox/>
    );
  }
});
```
BodyではUserBoxコンポーネントを表示するようにしました。
UserBoxコンポーネントは入力フォームとリストをまとめたコンポーネントです。

データのやり取りは下図の通りです。
![userbox.png](https://qiita-image-store.s3.amazonaws.com/0/89819/47615a95-b688-27a6-c80f-513abd5efcd2.png)


```js:body.jsx
//フォームとリストを一つにしたもの
var UserBox = React.createClass({
  getInitialState:function(){
    return {userData:[]};
  },
  handleAddUser:function(name, mail){
    var data = this.state.userData;
    data.push({name: name, mail: mail});
    this.setState({userData: data});
  },
  render:function(){
    return(
      <div style={{width:"300px"}}>
        <UserForm addUser={this.handleAddUser}/>
        <hr/>
        <UserList userData={this.state.userData}/>
      </div>
    );
  }
});
```
UserBoxコンポーネントでは、ユーザーデータの一覧を持てるよう、stateにuserDataを保持するようにしました。
初期値として、getInitialStateで空の配列を代入しておきます。

次にフォームで追加ボタンをクリックしたら、stateのuserDataに値が追加されるようにするためのイベントを作成しました（handleAddUser）。

最後にrenderですが、
handleAddUserがフォームの追加ボタンをクリックした時に呼ばれるようにしたいため、UserFormのpropsであるaddUserにhandleAddUserを指定しています。

また、一覧で表示されるデータとして、UserListのpropsであるuserDataに、UserBoxのstateであるuserDataを指定しています。

```js:body.jsx
//リストそのものを表示するコンポーネントを定義
var UserList = React.createClass({
  propTypes:{
    userData:React.PropTypes.arrayOf(React.PropTypes.object).isRequired
  },
  render:function(){
    var UserNodes = this.props.userData.map(function(user, index){
      return (
        <User name={user.name} mail={user.mail} key={index}/>
      );
    });
    return (
      <table>
        <tbody>
          <tr>
            <th>名前</th>
            <th>メールアドレス</th>
          </tr>
          {UserNodes}
        </tbody>
      </table>
    );
  }
});
```
リストを表示するコンポーネントでは、propsであるuserDataについて一行ずつUserコンポーネントとして作成し、積み上げたものをUserNodesという変数に代入するようにしています。

Userコンポーネントは以下の通りです。

```js:body.jsx
//リスト一行分を表示するコンポーネントを定義
var User = React.createClass({
  propTypes:{
    name: React.PropTypes.string.isRequired,
    mail: React.PropTypes.string
  },
  render:function(){
    return (
      <tr>
        <td>{this.props.name}</td>
        <td>{this.props.mail}</td>
      </tr>
    );
  }
});
```
propsとして定義された名前とメールアドレスを表示するようにしています。

最後に入力フォームは以下の通りです。

```js:body.jsx
//ユーザーの入力フォームを定義
var UserForm = React.createClass({
  propTypes:{
    addUser:React.PropTypes.func.isRequired
  },
  handleSubmit:function(){
    var name = ReactDOM.findDOMNode(this.refs.name).value.trim();
    var mail = ReactDOM.findDOMNode(this.refs.mail).value.trim();
    if (!name){
      return;
    }
    this.props.addUser(name, mail);
    ReactDOM.findDOMNode(this.refs.name).value = "";
    ReactDOM.findDOMNode(this.refs.mail).value = "";
  },
  render:function(){
    return (
      <div>
        <table>
          <tbody>
            <tr>
              <td>
                <label>名前</label>
              </td>
              <td>
                <input type="text" ref="name"/>
              </td>
            </tr>
            <tr>
              <td>
                <label>メールアドレス</label>
              </td>
              <td>
                <input type="text" ref="mail"/>
              </td>
            </tr>
          </tbody>
        </table>
        <div style={{textAlign:"right"}}>
          <button onClick={this.handleSubmit}>追加</button>
        </div>
      </div>
    );
  }
});

module.exports = Body;
```
追加ボタンをクリックした際のonClickイベントとしてhandleSubmitを定義しています。

handleSubmitではinputに入力された値を取得し、propsのaddUserイベントに値を渡しています。
ここでは、ReactDOM.findDOMNodeというのとrefsというのを使っています。

# refs
コンポーネントにはそれぞれ、refというコンポーネントを識別するための属性を持つことができます。
同一コンポーネント内の全てのrefはrefsというものにまとめられます。

上記のUserFormコンポーネントを例にすると、
`<input type="text" ref="name"/>`
`<input type="text" ref="mail"/>`
とrefが定義されていますが、UserFormコンポーネントで`this.refs.name`または`this.refs.mail`と指定すると、それぞれのコンポーネントにアクセスすることができるようになります。

コンポーネントのDOM要素を取得する場合は、ReactDOM.findDOMNodeを使用します。
getDOMNodeというのもあったのですが、Ver0.14で非推奨となっています。

```js
var name = ReactDOM.findDOMNode(this.refs.name).value.trim();
```
のように使用します。

※2018年最近のref事情についてまとめてみました。
React – ３つのref
https://solutionware.jp/blog/2018/07/25/react-%EF%BC%93%E3%81%A4%E3%81%AEref/

# サンプルソース
https://github.com/kunitak/react-tutorial/tree/day5

次回→[今からはじめるReact.js〜サーバーサイド〜](http://qiita.com/kuniken/items/799197256ab3a1e46bd4)