こんにちは!フロントエンドの現場を渡り歩いてきたチーフアーキテクトの私です。
Reactを触り始めの頃って、`useState`を覚えると「何でもかんでもとりあえず状態(State)に入れたろ!」ってなりがちなんですよね。めちゃくちゃ分かります。その気持ち、痛いほどよく分かります。
画面に出したいデータは全部`const [xxx, setxxx] = useState(…)`に入れちゃえば、なんだか動く気がしますよね。でも、ちょっと待ってください。その書き方、実は将来的に「あれ?なんかデータがズレるぞ…?」という泥沼のバグを引き起こす、隠れた爆弾になっているかもしれないんです。
今回は、React初学者のあなたが絶対に知っておくべき「派生状態(Derived State)」の考え方と、そのスマートな最適化について、お買い物のシチュエーションに例えて優しくお話ししていきますね。
大丈夫、一つひとつ紐解いていけば全然難しくありません。一緒に見ていきましょう!
—
1. 「全部Stateに入れちゃう病」の罠
まずは、よくある初心者の落とし穴からお話ししますね。
例えば、あなたが「お買い物アプリ」を作っているとしましょう。カゴの中に「商品の単価」と「個数」が入っています。
ここで画面に「合計金額」を表示したいとします。さて、この「合計金額」、あなたならどうやって持たせますか?
「よし、`totalPrice`っていうStateを作って、個数が変わるたびに計算して入れ直そう!」
……おっと、ちょっと待ったー!それ、やっちゃいがちなんですけど、Reactの世界ではご法度(アンチパターン)なんです。
例え話:お財布のレシートと電卓の関係
想像してみてください。お財布の中に「買ったもののリスト」が入っています。
わざわざその都度、新しく「合計金額メモ」を別の紙に書いて、お財布にしまっていきますか?
もし後から「やっぱりりんご1個減らしたわ」となったとき、商品のリスト(元データ)を書き換えて、さらに合計金額のメモ書きも自分で消して書き直さないといけないですよね。これって、めちゃくちゃ面倒だし、書き換え忘れが起きそうです。
プログラミングもこれと全く同じです。
- 元データ(真実のソース):商品の単価、個数
- そこから計算できるもの(派生状態):合計金額
元データがあるなら、合計金額は「必要なときに、その場で計算して出せばいい」んです。わざわざ別のStateとして持って、同期させる労力を生む必要はありません。
—
2. 派生状態(Derived State)ってなに?
難しい言葉が出てきましたが、要するに「すでにあるデータから計算して作り出せる値」のことです。
Reactにおいて、コンポーネントが再描画(レンダリング)されるたびに、その場でチャチャっと計算しちゃえばいいんです。Stateを増やす必要は一切ありません。
実際のコードで見てみましょう。
import React, { useState } from ‘react’;
function ShoppingCart() {
// 1. 本当に必要なデータ(State)はこれだけ!
const [price, setPrice] = useState(1000); // 商品の単価
const [count, setCount] = useState(1); // 個数
// 2. 【派生状態】stateからその場で計算する(変数として持たせる)
// わざわざ const [total, setTotal] = useState() なんてしなくてOKです!
const totalPrice = price count;
return (
お買い物カゴ
単価: {price}円
{/ 個数が変わるとコンポーネントが再描画され、totalPriceも自動で再計算されます /}
{count}
{/ その場で計算した結果を表示するだけ! /}
合計金額: {totalPrice}円
);
}
export default ShoppingCart;
どうでしょう?これだけで、個数を変えたときに勝手に合計金額が変わる仕組みができました。`setTotalPrice`みたいな更新関数を書く必要がないので、バグる余地がありませんよね。最高にシンプルです。
—
3. 「でも、計算がめちゃくちゃ重いときはどうするの?」問題
「なるほど、その場で計算すればいいのね!」と思ったあなた、鋭いですね。
もしその計算が、1万件のデータをこねくり回すような、CPUを激しく消耗する重い処理だったらどうでしょう?
Reactは、画面のどこかのStateがちょこっと変わるたびに、コンポーネントの中身を上から下までもう一回実行(再描画)します。つまり、ボタンを押すたびに重い計算が毎回走ってしまうことになり、アプリがカクカクする原因になります。
ここで登場するのが、今回のもうひとつの主役「`useMemo`(メモ化)」です。
`useMemo`ってなぁに?
一言で言うと、「前回の計算結果を頭の片隅にメモ(記憶)しておいて、材料が変わってないなら、計算をサボって前回の結果をそのまま使い回す魔法の箱」です。
お買い物に例えるなら、店員さんが「あ、さっき計算したから、同じ個数なら前のレシートの金額をそのまま使うね!」と計算をスキップしてくれるようなものです。
先ほどのコードに`useMemo`を組み込んでみましょう。
import React, { useState, useMemo } from ‘react’;
function HeavyShoppingCart() {
const [price, setPrice] = useState(1000);
const [count, setCount] = useState(1);
const [couponCode, setCouponCode] = useState(”); // 関係のないState
// 重い計算(今回は単純な掛け算ですが、イメージとして捉えてください)
// useMemoを使うことで、priceかcountが変わった時だけ計算が走るようになります!
const totalPrice = useMemo(() => {
console.log(‘💥 重い計算が走っています…!’);
// 想像してください:ここで何千行もの複雑なデータ処理をしているとします
let result = price count;
return result;
}, [price, count]); // ← この「依存配列」の中に書いた値が変わった時だけ、再計算されます
return (
最適化されたお買い物カゴ
{/ 全く関係のないStateを更新してみても… /}
/>
合計金額: {totalPrice}円
※コンソールのログを見てみてください。クーポンコードを入力しても「重い計算」は走っていません!
);
}
export default HeavyShoppingCart;
チーフアーキテクトからの大切なアドバイス:
ここで一つ、シニアエンジニアからの現場のリアルなアドバイスを。「じゃあ、全部の計算に`useMemo`をつけまくれば最強じゃん!」と思っちゃダメですよ。
実は、`useMemo`自体にも「前回の依存する値を記憶しておくためのメモリ」や「値が変わったかチェックする小さなコスト」がかかっています。
だから、今回の例のような軽い足し算・掛け算ごときに`useMemo`を使うのは、かえってメモリの無駄遣い(オーバーヘッド)になることがあります。
- 軽い計算:そのまま変数で書く(`const total = a b;`)
- 重い計算や、子コンポーネントに余計な再描画をさせたくないオブジェクトの作成:`useMemo`を使う
このメリハリがつけられるようになると、あなたの書くReactコードは一段と美しく、プロっぽくなりますよ!
—
まとめ
今回のポイントをギュッと凝縮して振り返ってみましょう。
1. 既存の状態から作れる値は、新しいStateにしない(派生状態にする)
- データが二重管理になってバグの温床になるのを防ぐため、その場で計算して出そう。
2. 計算が重いときだけ、`useMemo`でメモ化する
- 無駄な再計算を防いでアプリのパフォーマンスを守ろう。ただし、何でもかんでも貼るペタ病には注意!
最初は「どこまでをStateにして、どこからを計算すべきか」迷うこともあると思います。でも大丈夫、場数を踏めば自然と手が覚えるようになります。
「あ、これって別のデータから計算できるな」と気づけた瞬間、あなたはもうReactの仕組みを一つ深く理解できた証拠です。その調子で、楽しくモダンなフロント開発を続けていきましょう!
それでは、また次の現場でお会いしましょう!チーフアーキテクトの私でした。

コメント