es6
reactjs
React

Reactのstate,props,componentの使い方を自分なりにまとめてみた

オープンストリーム Advent Calendar 2017 13日目の記事になります。

業務でReactを使い始めて約4ヶ月が経ち、ようやく思い通りに実装できることが増えてきたので、
本記事では個人的に重要だと思う点、実装でつまずいた点を紹介します。

stateの使い方

コンポーネント内で使用できる値です。
値が変更されるとrenderが走ります。

コンストラクタで初期値を割り当て、

constructor(props) {
  super(props);
  this.state={
      x: 0,
  };
}

setStateで更新します。

this.setState({x: 1});

次のような書き方は再レンダリングされないため、非推奨とされています。

this.state.x = 1;

配列の場合も、push等で直接変えると再レンダリングされません。
私は次のような書き方をしてハマったのでご注意下さい。

this.state.sample.push(num);
this.setState({sample:this.state.sample});

方法はいくつかありますが、immutability-helperを使うと次の書き方で再レンダリングされます。

import update from 'immutability-helper';
/****
省略
****/
this.setState({
  sample: update(this.state.sample, {
      $push: [num],
    }),
  });
}

また、次のような場合は更新に失敗することがあるようです。 

this.setState({
  counter: this.state.counter + this.props.increment,
});

最初の引数として前のstateを受け取り、そこへpropsの値を足す書き方が推奨されています。

this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

propsの受け渡し

別のコンポーネントに値やコールバック関数を渡すことができます。
親コンポーネントから子コンポーネントにpropsを渡すときは、

<Child props={value}/>

のように書きます。
この場合、子コンポーネント(Child)では、渡した値(value)をpropsという名前で扱うことができるようになります。

具体的には次のような使い方をします。

import React from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Parent.css';
import Child from '../../components/Child';

// 親コンポーネント
class Parent extends React.Component {

  // コンストラクタで初期値をセット
  constructor(props) {
    super(props);

    // この例では関数内でthisを使用するため、thisをbind
    this.bindFunc = this.func.bind(this);

    // stateの初期値を設定
    this.state = {
      str: '',
      isDisabled: true,
    };
  }

  // コールバック関数
  func(str) {

    //子コンポーネントから受け取った値を元にisDisabledを更新
    if (str.length > 10) {
      this.setState({ isDisabled: false });
    } else {
      this.setState({ isDisabled: true });
    }
  }

  // 子コンポーネントにfuncという名前でthisをbindした関数を渡す
  render() {
    return (
      <div className={s.root}>
        <Child func={this.bindFunc} />
        <input
          type="button"
          disabled={this.state.isDisabled}
          value={'submit'}
        />
      </div>
    );
  }
}

export default withStyles(s)(Parent);
import React from 'react';
import PropTypes from 'prop-types';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Child.css';

// 子コンポーネント
class Child extends React.Component {
  static propTypes = {
    func: PropTypes.func.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      str: '',
    };
  }

  // ボタンが押された時にコールバック関数を呼び出す
  onClick = () => {
    this.props.func(this.state.str);
  };

  // テキストボックスの内容が変更された時にstateを更新
  onChange = e => {
    this.setState({ str: e.target.value });
  };

  render() {
    return (
      <div className={s.root}>
        <input type="text" onChange={e => this.onChange(e)} />
        <input type="button" onClick={() => this.onClick()} value={'ok'} />
      </div>
    );
  }
}

export default withStyles(s)(Child);

この例ではfuncがコールバック関数です。
これを呼び出すことによって、子コンポーネントの変化を親コンポーネントに伝えることが出来ます。
ここでは、子コンポーネントで更新した文字列を親コンポーネントへ伝える役割をしています。

(関数を渡す場合はrender内でbind(this)すると再度renderが走ってしまうため、constructor内に記述することをおすすめします。)

ReactComponentライフサイクル

Reactを触り始めた頃、これを知らずにどう開発していたのか不思議なくらいよく使うので、覚えておくことをお勧めします。

メソッド名 呼び出されるタイミング
componentWillMount() マウントが行われる直前
componentDidMount() マウントが行われた直後
componentWillReceiveProps() マウントされたコンポーネントが新しいpropsを受け取る前
shouldComponentUpdate() 新しいpropsやstateを受け取った時
componentWillUpdate() 新しいpropsやstateを受け取った時、レンダリングする直前
componentDidUpdate() 更新が行われた直後
componentWillUnmount() コンポーネントがアンマウントされ、破棄される直前
componentDidCatch() エラー発生時

以下の4つはよく使うので、使い方を紹介します。

  • componentWillMount()
  • componentDidMount()
  • componentWillReceiveProps()
  • shouldComponentUpdate()

componentWillMount()

render前に初期値を設定したい時などに使います。

componentWillMount() {
  this.setState({
    str: 'sample message'  
  });
}

componentDidMount()

Render直後に行いたい処理を書く時などに使います。
ここで同期的にsetStateすることは、再度renderを走らせてしまうため推奨されていません。必要な場合、コールバックやPromiseを介して非同期的にsetStateを呼び出すようにします。
setStateを使う場合は無限ループになってしまわないよう注意が必要です。

componentDidMount() {
  if(this.props.isShow){
    this.show();
  }
}

componentWillReceiveProps()

変更前後のpropsやstateを比較して処理を決定できます。

componentWillReceiveProps(nextProps) {
  if (this.props.sample !== nextProps.sample) {
    this.setState({ sample: nextProps.sample });
  }
}

shouldComponentUpdate()

値の変更後にrenderを走らせるか決定できます。

shouldComponentUpdate() {
  if(this.state.update) {
    return true;
  }
  else {
    return false;
  }
}

さいごに

Reactを使っていてよくぶつかるのが、renderが想定しているタイミングで走ってくれない・・・という問題です。1回でいいのに複数回走ったり、微動だにしなかったり、renderが走る条件や、stateやpropsが変更されるタイミングを覚えておくとスムーズに実装できるようになると思います。

また、間違っている部分があればご指摘いただけるとありがたいです。

参考

React公式Docs
https://reactjs.org/docs/state-and-lifecycle.html

React component ライフサイクル図
https://qiita.com/kawachi/items/092bfc281f88e3a6e456