【実務・中級編】 useStateの基本構文と初期化 – React実践ガイド

やあ。最近、チームのコードレビューをしていて、`useState`の初期化や更新処理のあたりで「おや?」と首を傾げたくなる実装にぶつかることが増えてきた。

中級へのステップを駆け上がっている君たちなら、コンポーネントの中で `const [state, setState] = useState()` と書けば状態が持てることくらい、呼吸をするように分かっているはずだ。しかし、「なぜその書き方をするのか」「裏でReactがどう動いているのか」を突き詰めていくと、実務で踏み抜くバグの数が劇的に減る。

今回は、その `useState` の基本中の基本、そして「初期化」という一見地味なテーマにスポットを当てて、シニアの視点から現場の知見をガッツリ伝授しよう。

—

1. なぜ `useState` は配列を返すのか?(JavaScriptの言語的側面)

まずは基本の復習からだ。以下のコードを見てほしい。

const [count, setCount] = React.useState(0);

ここで使われているのは、JavaScriptの配列の分割代入(Destructuring assignment)だ。
オブジェクトではなく「なぜ配列なのか?」と疑問に思ったことはないだろうか。

オブジェクトの分割代入であれば、プロパティ名を指定する必要がある。例えばこんな感じだ。

// もしオブジェクトだったら…(架空のAPI)
const { state: count, updateState: setCount } = useState(0);

これだと、コンポーネント内で同じフックを何回も呼ぶときに、変数名をリネームする手間が生まれて非常にめんどくさい。

const { state: count, updateState: setCount } = useState(0);
const { state: user, updateState: setUser } = useState(null);

だるいよな。配列であれば、インデックスの順番さえ合っていれば、呼び出し側で自分の好きな変数名を自由に付けられる。

const [count, setCount] = useState(0);
const [user, setUser] = useState(null);

Reactチームのこういった「開発者のタイピング数を減らし、可読性を上げる」ための細かい気配りは、本当にリスペクトに値する。

—

2. 初期値設定の罠:静的初期化と「遅延初期化」

さて、ここからが本題だ。実務で一番差が出るのが「初期値(Initial State)をどう与えるか」という点。

例えば、初期値として「重い計算処理の結果」や「ローカルストレージからの読み込み」を行いたいとする。

やりがちなアンチパターン(毎回実行される罠)

// ❌ やりがちなダメパターン
function UserProfile({ userId }) {
// heavyCalculation() は、コンポーネントが再レンダリングされるたびに毎回実行される!
const [userData, setUserData] = useState(heavyCalculation(userId));

return

{userData.name}

;
}

「あれ? `useState(initialValue)` って最初のマウント時にしか使われないんじゃないの?」と思ったそこの君。鋭いが、JavaScriptの仕様を思い出してほしい。

Reactがコンポーネントを再レンダリングするとき、その関数コンポーネント自体がもう一度頭から実行される。つまり、`heavyCalculation(userId)` という関数呼び出しのコード自体は、再レンダリングのたびに毎回評価(実行)されているのだ。その戻り値が捨てられているだけで、CPUやメモリの無駄遣いには変わりない。

ベストプラクティス:関数を渡す「遅延初期化(Lazy Initial State)」

重い処理や、初期化コストが高いデータを初期値にしたい場合は、`useState` に直接値を入れるのではなく、関数を渡す。これが鉄則だ。

// ⭕ 正しい遅延初期化のパターン
function UserProfile({ userId }) {
// 初期化用の「関数」を渡す。この関数は初回マウント時の一度だけ実行される!
const [userData, setUserData] = useState(() => {
console.log(“重い初期化処理が走りました(初回のみ)”);
return heavyCalculation(userId);
});

return

{userData.name}

;
}

引数なしの関数(アロー関数など)を `useState` に渡すと、Reactはその関数を初回マウント時の一度だけ実行し、その戻り値を初期状態として採用する。再レンダリング時にはこの関数は完全に無視される。この小さな違いが、大規模なアプリケーションのパフォーマンスをじわじわと支えていくんだ。

—

3. 実務でそのまま使える!クリーンなサンプルコード

百聞は一見に如かず。ここまで解説した「配列の分割代入による綺麗な宣言」と「遅延初期化」を組み合わせた、実務レベルのコンポーネントを置いておく。

ローカルストレージから設定を読み込む、よくあるダッシュボードのウィジェットを想定してくれ。

import React, { useState } from ‘react’;

// 【模擬】ローカルストレージから重い設定データを読み込む初期化関数
const loadInitialSettings = () => {
console.log(‘— ローカルストレージへのアクセス(重い処理) —‘);
const savedSettings = localStorage.getItem(‘app_settings’);

if (savedSettings) {
try {
return JSON.parse(savedSettings);
} catch (e) {
console.error(‘JSONのパースに失敗しました’, e);
}
}

// デフォルト値
return { theme: ‘light’, notifications: true };
};

export const SettingsWidget = () => {
// 1. 配列の分割代入で、状態変数と更新関数をわかりやすく命名
// 2. 引数に「関数」を渡すことで、初期化コストを初回マウント時のみに限定(遅延初期化)
const [settings, setSettings] = useState(() => loadInitialSettings());

const [inputCount, setInputCount] = useState(0); // ついでに普通の数値ステート

// テーマを切り替えるハンドラー
const toggleTheme = () => {
setSettings((prevSettings) => {
const newSettings = {
…prevSettings,
theme: prevSettings.theme === ‘light’ ? ‘dark’ : ‘light’,
};

// ついでにストレージにも保存
localStorage.setItem(‘app_settings’, JSON.stringify(newSettings));
return newSettings;
});
};

return (

ユーザー設定パネル

現在のテーマ: {settings.theme}


{/ 再レンダリングの挙動を確認するためのカウンター /}

再レンダリング確認用カウンター: {inputCount}

);
};

—

シニアからのメッセージ

現場でコードを書いていると、「動けばいいや」で `useState(JSON.parse(localStorage.getItem(…)))` のようなコードを書いてしまいがちだ。しかし、そういった細部へのこだわりを積み重ねることが、のちに「なんかこのアプリ、妙にキビキビ動いて気持ちいいぞ」というユーザー体験の差になって返ってくる。

`useState` はただの箱じゃない。Reactという強力なライブラリが裏側でメモリを管理し、適切なタイミングでUIを同期させるための「神聖なインターフェース」だ。
仕組みの本質を理解し、一歩進んだ美しいコードを書いていこう。

さて、次は「非同期バグ対策(状態更新のキューイングと関数型アップデート)」について深掘りする予定だ。心してついてきてくれよ!

コメント

タイトルとURLをコピーしました