React公式ドキュメントを読む14 stateを使用する際の注意点3つ

前回からの続きですが、このページだけ読んでも分かる内容になっています。

コンポーネント内でstateを使用する際の注意点3つ

  1. state は直接更新せずに、setState() を使用して更新する
  2. this.state を更新するタイミングはReactが決める
  3. state のなかの一部の変数が更新されることがある

1. state は直接更新せずに、setState() を使用して更新する

コンポーネント内でstateを使用する場合、最初に実行されるコンストラクタのなかでは state に値を直接代入して初期化できますが、そのあとにstateを更新する際には直接更新はできません。stateを更新する際には直接更新するのではなく、代わりに setState() を使用します。

例えば、以下のコードは誤りです。このコードでは、Reactが state が更新されたことを検知できないために再レンダーされません。Reactは setState() が実行されたことをきっかけに state が更新されたことを検知します。stateを直接更新してしまうと、Reactが再レンダーするきっかけが与えられません。

// 誤った書き方
this.state.comment = 'Hello';

state を変更する際の正しいコードは以下になります。stateを直接変更するのではなく、代わりに setState() を使用します。setState() を使用することで、 Reactが state が更新されたことを検知して、更新された部分を再レンダーできるようになります。

// 正しい書き方
this.setState({comment: 'Hello'});

this.state に直接代入してよい唯一の場所はコンストラクタです。それ以外の場所では setState() を使用して state を更新してください。

2. this.state を更新するタイミングはReactが決める

Reactでは、this.state の更新が非同期に行われることがあります。これは、Reactがパフォーマンスを高めるために、複数の setState() の呼び出しを一度にまとめて処理することがあるからです。そのため、stateの値を求める際には、 this.props、および、this.state の値に依存するべきではありません。

例えば、以下のコードは誤りです。このコードでは、counterをインクリメントするのに this.props や this.state を利用しています。this.props、および、this.state の値を更新するタイミングはReactが決めるため、これらの値に依存したプログラムではカウンターの値が期待しない挙動になる場合があります。

// 誤った書き方
this.setState({
  counter: this.state.counter + this.props.increment,
});

正しいコードは以下になります。以下のコードでは、 setState() の第1引数にこの時点でのstateを、第2引数にこの時点でのpropsを指定しています。これらの現時点での引数を元にしてcounterをインクリメントしています。

Reactがどのタイミングで更新するのか分からない this.state や this.props を使うのではなく、何が入っているのか明確な現時点でのstate や 現時点でのprops を引数として与えることで、信頼できる値を使ってカウンターがインクリメントされます。

// 正しい書き方
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

上記のコードをアロー関数を、通常の関数に書き換えると以下になります。上記のコードはES6によるモダンな記法、以下のコードはES5によるレガシーな記法という違いはありますが、コードが実行する内容は同じです。

重要なのは state や props を引数として与えていて、 this.state や this.props を使っていないという点です。以下のコードも正しく動作します。

// 正しい書き方 ※通常の関数を使ったコード
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});

3. state のなかの一部の変数が更新されることがある

state には、いくつかの独立した変数を含んでいる場合があります。そして、そのなかの一部の変数が更新されることがあります。

例えば、以下のコードでは、stateは、postsとcommentsという2つの独立した変数を含んでいます。

  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }

上記の場合、例えば以下のように setState() を別に呼び出して、それぞれの変数を独立して更新できます。

  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

上記のコードでは、 this.setState({posts}) は、this.state.posts を完全に置き換えて、this.state.comments をそのまま残します。また、 this.setState({comments}) は、this.state.posts をそのまま残して、this.state.comments を完全に置き換えます。

つまり、ひとつの state に含まれる2つの変数 postsとcomments は、それぞれ独立して更新されます。

コンポーネント内でstateを使用する際の注意点まとめ

以上、コンポーネント内でstateを使用する際の注意点を3つ紹介しました。これらの注意点から言えることは、Reactにおけるstateは柔軟なので、取り扱いには注意が必要ということです。

Reactにおけるstateは、操作の順序やタイミングなどによって変化していくものです。変数に this.state を代入すれば、いつでも同じ値が取得できるわけではないので、どの時点のstateなのかを意識しながら取り扱う必要があります。

次回へ続きます。