Reactを勉強し始めた時に最初に覚えるのが「props」だと思います。
英語なので馴染みにくいですが、「関数でいう引数みたいなもんかな?」くらいまでイメージできれば十分ではないでしょうか?
自分もReactを使い始めた時に「propsって便利だな」と感じたのですが、便利さが故にミスると後で少々めんどいことになったので注意点を1点共有します。
「propsで出来ることくらいは何となく分かっている」
「propsで突然エラーが出ることがあるけどイマイチ原因が分かっていない」
「時々Warningっていうメッセージがコンソールに出てるけど動作はしている」
本日はそんな方に見てもらえたら良いかもしれません。
また動画も用意しているので適時使ってください。
Reactのpropsの渡し方であり得るミス
最初に自分もやったことのあるpropsの良くない渡し方を紹介します。
心当たりのある方は要注意です。
【親コンポーネント:Counter.js】
import React, { useState } from "react";
import CounterCom from "../components/CounterCom";
const Counter = () => {
const [count, setCount] = useState(0);
return (
<>
<CounterCom count={count} setCount={setCount} />
</>
);
};
export default Counter;
【子コンポーネント:CounterCom.js】
import React from "react";
const CounterCom = ({ count, setCount }) => {
return (
<div>
<button onClick={() => setCount((num) => num - 1)}>-</button>
<span>{count}</span>
<button onClick={() => setCount((num) => num + 1)}>+</button>
</div>
);
};
export default CounterCom;
【動作結果】
結論から言うと、こちらは「子のステートを親で管理してpropsで子に戻す」ことをやっています。
こちら特に問題なく動作しますし、初心者向けのYoutubeでこのように紹介している例はそこそこあります。
理解が深まっていないうちは、出来るだけステートは同じコンポーネント内で管理する方が好ましいです。
なぜなら「使い回す」というコンポーネントにおける最大のメリットが使いづらくなるからです。
上記の例だと子コンポーネントの「CounterCom.js」にはカウンターの部品がまとめられているのですが、「Counter.js」以外でも使いたくなったときに簡単に使えなくなっています。
「CounterCom.js」のステートが「Counter.js」で管理されてしまっているからです。
それだけではありません。
「Counter.js」の中だけ使ったとしても以下のような修正を加えると思ったように動作しなくなります。
【親コンポーネント:Counter.js】
import React, { useState } from "react";
import CounterCom from "../components/CounterCom";
const Counter = () => {
const [count, setCount] = useState(0);
return (
<>
<CounterCom count={count} setCount={setCount} />
<CounterCom count={count} setCount={setCount} />
</>
);
};
export default Counter;
子コンポーネントである「CounterCom.js」を2回呼ぶように変更してみました。
そうすると1個のカウンターしか触っていないのに2個のカウンターが連動して動いてしまいます。
【動作結果】
小規模な開発や初期学習であれば問題ないですが、早い段階で上記のような書き方は見直すようにしましょう。
Reactのステートはpropsで渡さなくても良いように同一コンポーネントで管理する
先ほどの例を書き直してみたのがこちらになります。
【親コンポーネント:Counter.js】
import React from "react";
import CounterCom from "../components/CounterCom";
const Counter = () => {
return (
<>
<CounterCom />
</>
);
};
export default Counter;
【子コンポーネント:CounterCom.js】
import React, { useState } from "react";
const CounterCom = () => {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount((num) => num - 1)}>-</button>
<span>{count}</span>
<button onClick={() => setCount((num) => num + 1)}>+</button>
</div>
);
};
export default CounterCom;
【動作結果】
「Counter.js」のなかでuseStateを使ってステート管理していた部分を「CounterCom.js」に移動させました。
それに伴ってpropsも必要なくなったので削除しています。
まずコード自体がスッキリして可読性が少しばかり向上したのと、何よりステートを子コンポーネントで完結させているので使い回しができるようになっています。
「Counter.js」で「CounterCom.js」を2個呼んでみましょう。
【親コンポーネント:Counter.js】
import React from "react";
import CounterCom from "../components/CounterCom";
const Counter = () => {
return (
<>
<CounterCom />
<CounterCom />
</>
);
};
export default Counter;
【動作結果】
カウンターがそれぞれ独立して動作するようになりました。
これこそコンポーネントに分けることのメリットですね。
ちなみに今回の件はprops自体が問題というより、JavaScriptファイル同士でimportできるが故にステート管理があやふやになったことが原因でした。
props自体は値や関数を簡単に引き継ぐことのできる便利な機能なので、怖がらずにどんどん使っていってもらえればと思います。