こんにちは、sanagiです!
最近、去年Reactで開発したアプリの不具合対応をしています。
私自身React歴浅いまま開発に携わったため、こう書いたら良かったな~と思う箇所をよく見かけて、うーん、、🤔となります。
今回は、そういった自分の反省もかねてこの記事を書いていきます。
①useEffect使い過ぎ問題
このuseEffect関連のバグ、結構多かった気がします。
公式でもuseEffectに関しては以下のように記載されており、あくまで「外部システムと同期するためのもの」、、ということで、極力使わないでという意思を強く感じますね😐
特に厄介だったのは、useEffectが毎回同じタイミングで実行というわけではない(再現性がない)ため、dev環境でしか発生しないような不具合もあったことです。一見正しく動いているように見えて、何回かに1回バグる、、みたいな場合は、useEffectの可能性が高いと思います。
サクッとuseEffectを減らせる例を紹介するとしたら、props/stateを元に別のstateを更新するような場合、useEffectを使わなくても書けることが多いので、そういった部分は積極的にリファクタできるのかなと思います。
例えば、以下のようなfirstNameとlastNameを足してfullNameを更新する場合、以下のようにuseEffectを使っている部分は、
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ...
}
こんな形で修正することができます。
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const fullName = firstName + ' ' + lastName;
// ...
}
そこら辺のuseEffectの改善方法が公式に載っているので一度読んでみると良いと思います。
参考コードも載っており結構分かりやすかったです。最後に課題問題もあり勉強になります。
⇒そのエフェクトは不要かも
あと、useEffect関連のバグはStrictMode(useEffectが2回実行される)を有効化すると見つけやすくなるようですね。
②Memo化を知らんかった
React開発のプロジェクト前半頃は、ちゃんとMemo化を知らなかったので無駄にstateを作っていました。
こんな感じで⇓
APIから取得したデータを加工して表示用のデータに生成したいとき、①取得データ用のstateに入れて⇒②取得したデータのstateが更新されたら加工して⇒③表示用のstateを更新して・・
みたいに書いていました。
const Component = () => {
const [id, setId] = useState(0);
const [data, setData] = useState([]); // APIから取得したデータを保持
const [selectDataList, setSelectDataList]; // セレクトボックス用のデータを生成
// APIからデータを取得してdataをセットする
useEffect(() => {
const loadData = async () => {
const data = await fetchData(id);
setData(data);
}
loadData();
}, [id]);
// セレクトボックス用のデータを生成
useEffect(() => {
const listData = ~~dataをなんか加工する処理~~;
setSelectDataList(listData);
}, [data]);
return <div>...</div>;
}
でもこれだと、useEffectも無駄だし、stateも無駄に作ってしまってます。
ですが、Memo化を使うと以下のように修正できます。
const Component = () => {
const [id, setId] = useState(0);
const [data, setData] = useState([]); // APIから取得したデータを保持
// APIからデータを取得してdataをセットする
useEffect(() => {
const loadData = async () => {
const data = await fetchData(id);
setData(data);
}
loadData();
}, [id]);
// セレクトボックス用のデータを生成
const selectDataList = useMemo(() => {
return ~~dataをなんか加工する処理~~;
}, [data]);
return <div>...</div>;
}
useMemoを使うと、依存配列に入れているstateが更新されたときに再計算をしてくれます。
なので、dataに変更がなかったら同じ値を返し、dataに変更があれば再計算した値を返してくれる。キャッシュのような役割をやってくれます。
特にバグを産んだというわけではないですが、無駄なstateとeffectを
こちらも公式のリンク載せます。⇒useMemo
③stateを直接変更してしまう
これも、結構初期の頃にやってしまったやつなのですが、state自身に破壊的処理を行ってしまう、、という初歩的なミスをしてしまいました。
公式(>>state 内のオブジェクトの更新)にも書いてありますが、基本的にstateは読み取り専用であり、直接変更してはいけないことになってます。
なのですが、初期の頃はそれを意識できておらず、削除イベントを作成する部分で、配列のstateから直接要素を削除する処理を書いてしまいました。
const [dataList, setDataList] = useState([]);
// 対象のindexデータを1件削除
const deleteDataEvent = (index) => {
dataList.splice(index, 1); // 配列のstateに直接的な変更を加えている
}
このとき、どんな動作不具合があったかはっきり覚えてはいないのですが、stateに直接的に変更を行うと、データは消えているのに画面が更新されなかったり、何回目かの操作で動作がおかしくなったりが頻出するようです。
これは、以下のように修正できます。
const [dataList, setDataList] = useState([]);
// 対象のindexデータを1件削除(indexと一致していないものだけをフィルタ)
const deleteDataEvent = (index) => {
setDataList(items => items.filter((_, i) => i !== index));
}
filterにすることで、直接stateに変更を加えることなく、setStateすることができます。
おわりに
去年はReactの開発を数か月やりましたが、奥が深いというか、今もほとんどhookを使いこなせていないなとよく思います。
Reactをよく分からないまま書いても、とりあえずは動くようにできてしまうのが、Reactの怖いところだと思います。
よく分からないまま作って、気づいたらバグが出やすくなっていたり、重たい処理になっていたりが本当に発生しやすいと思うので、そこら辺気を付けていきたいですね。(最近はAIも発達してきているので、そこら辺も活用しつつ、、)

