347
295

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

オープンストリームAdvent Calendar 2017

Day 13

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

Last updated at Posted at 2017-12-13

オープンストリーム 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を触り始めた頃、これを知らずにどう開発していたのか不思議なくらいよく使うので、覚えておくことをお勧めします。

2018/09/28追記
※v16.3よりcomponentWillMount、componentWillReceiveProps、componentWillUpdateが非推奨となりました。v17.0で削除される予定なので、v17.0以降も使用する場合はUNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps、UNSAFE_componentWillUpdateに変更する必要があります。(2018/09/28現在の最新はv16.5.4です)
(ご指摘ありがとうございました。)
移行方法:https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
v16.3.0リリースノート日本語訳:https://mae.chab.in/archives/60040

メソッド名 呼び出されるタイミング
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

347
295
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
347
295

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?