Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

前回→今からはじめるReact.js〜React ver0.14〜

propsとstateの特徴

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

  • props
  • state

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

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

値を指定するタイミング 値の変更可否
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

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

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

値の取得

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

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

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

初期値の指定

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

以下のようにします。

props

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

state

var Component = React.createClass({
  getInitialState: function() {
    return {
      stateKey1: 'initial value'
    };
  },

stateの場合はファンクション名の通り、初期値を設定するイメージですね。

propsの型・必須指定

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

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

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

不正な値を渡したり、指定必須なのに値を渡していなかったりすると、以下のように、デベロッパーツールのコンソールにエラーが表示されます。
スクリーンショット 2015-10-12 19.06.10.png

フォームとリストを作成してみる

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

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

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

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

//ボディの定義
var Body = React.createClass({
  render: function(){
    return (
      <UserBox/>
    );
  }
});

BodyではUserBoxコンポーネントを表示するようにしました。
UserBoxコンポーネントは入力フォームとリストをまとめたコンポーネントです。

データのやり取りは下図の通りです。
userbox.png

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を指定しています。

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コンポーネントは以下の通りです。

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として定義された名前とメールアドレスを表示するようにしています。

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

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で非推奨となっています。

var name = ReactDOM.findDOMNode(this.refs.name).value.trim();

のように使用します。

※2018年最近のref事情についてまとめてみました。
React – 3つの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〜サーバーサイド〜

kuniken
React,Nodejs,Salesforceをよく使ってます。 ーー エンジニア募集中です! https://paiza.jp/career/job_offers/5412 システムに関するご相談はこちらより承っております。 https://solutionware.jp/contact.html
https://solutionware.jp/blog/category/kunitak/
solutionware
WEBサービスの開発・運営を通じてお客様の生産性向上を実現します
https://solutionware.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away