ham-capのブログ

プログラミング学習の記録

【React】propsで受け取ったデータを自身のstateにコピーしてはならない

先日、フィヨルドブートキャンプのプラクティスにReactが追加されました。 自分のタイミング的にもちょうどよかったので、早速入門してハマり倒しています。

というわけで、今回はReactにおけるpropsの扱い方に関して気づいたことを書きます。

結論としてはタイトル通りですが、詳しく見ていきます。

propsって何?

詳しくは公式ドキュメントを見ていただくとして、簡単に言うとコンポーネントが外部からの入力を受け取るときに使用する専用の箱のようなものです。

また、propsはオブジェクトです。

値を渡すときはこんな感じ。

<MyComponent foo="hoge" />

MyComponentというコンポーネントに、キーがfoo、バリューがhogeのセットを渡しています。

つまり、MyComponentのpropsには{foo: "hoge"}が渡っています。

したがって、MyComponent内でこれを参照する際は以下のようになります。

console.log(this.props.foo);
// => hoge

stateって何?

stateはコンポーネントが持つ状態のことです。

ユーザーからの入力に応じて変わるコンポーネントの状態を管理するために使います。

詳しくはまたしても公式ドキュメントをご参照いただきたいのですが、例えばユーザーが入力フォームに文字を入力するたびに、そのフォームに紐づいたstateをリアルタイムに更新する、といったことが実現できます。

コンポーネントのstateを子コンポーネントのpropsに渡すことができる

Reactではコンポーネントの中で別のコンポーネントを呼び出すことができます。

仮にコンポーネントAがコンポーネントBを呼び出しているとき、Aが親でBが子という関係になるのですが、このときAのstateをBのpropsに渡し、Bの内部で参照することができます。

では、Aのstateがユーザーの入力によって更新された場合どうなるでしょう。

Reactでは、Bが受け取ったpropsもきちんと更新されます。とても便利。

ただし、これには落とし穴もあります。

propsで受け取ったデータを自身のstateにコピーしてはいけない

ここからが本題です。

公式ドキュメントのこのページに以下のように書いてあります。

props を state にコピーしないでください。これはよくある間違いです。

constructor(props) {
 super(props);
 // してはいけません
 this.state = { color: props.color };
}

この問題はそれが不要(代わりに this.props.color を直接使用することができるため)であり、バグの作成につながる(color プロパティの更新は state に反映されないため)ことです。

つまり、親から受け取ったpropsを子が自分自身のstateとして保持すると不具合の原因になるということです。 前項に倣って親をA、子をBとして説明します。

何がまずいかというと、Aのstateが更新されたことをBが検知できなくなることです。

なぜそうなるかというと、この場合のBのstateはあくまでもコンストラクタが呼ばれた時点のpropsをコピーしたものであって、Aから渡されたpropsをリアルタイムに参照しているわけではなくなるからです。

こうなると、Bのコンストラクタが再度呼ばれてpropsが再度コピーされない限りは、せっかくReactがAのstateの更新をBに伝えてあげても無視してしまうことになります。

このことについて公式では、

意図的にプロパティの更新を無視したい場合にのみ、このパターンを使用してください。

と明言されているため、基本的にこのような書き方をするメリットは無いと思ってよさそうです。

解決策

これを回避するためにはどうすればいいかというと、素直にBの中でもpropsをそのまま使用すればよいだけです。

わざわざコンストラクタでstateの初期値としてコピーせずとも、this.props.fooのように渡されたpropsをそのままコンポーネント内で参照すればOKです。

まとめ

僕はまだReact初心者なのでちょっとしたことでもハマってしまうのですが、今回のテーマについては、Reactの利点を見事に潰してしまうようなポイントであるにもかかわらず、結構やりがちなんじゃないかなと思ったので真っ先に記事にしてみました。

同じようなところでハマっている初心者の方のお役に立てれば幸いです。