保守性が下がるReactアンチパターンを解説
Reactアンチパターンを知っておくと、実務で保守しにくいコードを避けやすくなります。
そのため、Reactは自由度が高いライブラリである一方、設計の差が出やすい技術でもあります。同じ画面でも、コンポーネントの分け方、stateの置き場所、useEffectの使い方によって、読みやすさや修正しやすさが大きく変わります。
学習段階では「動けばOK」でも、実務ではそれだけでは不十分です。機能追加、バグ修正、引き継ぎ、リファクタリングが続くため、後から変更しやすい設計が重要になります。
この記事では、React初心者や実務経験の浅いエンジニア向けに、保守性が下がるReactアンチパターンを10個紹介します。悪い例と改善例を見ながら、React・Next.js案件で意識したい設計の考え方を整理します。
Reactアンチパターンを避けるとなぜ保守性が上がるのか
まず、Reactにおける保守性とは、簡単に言うと後から変更しやすいことです。
例えば、1つのコンポーネントにAPI取得、入力管理、バリデーション、一覧表示、モーダル制御をすべて書くと、最初は動いても、後から修正するときに影響範囲が読みにくくなります。
保守性が高いReactコード
読みやすい ↓ 変更しやすい ↓ テストしやすい ↓ 引き継ぎやすい
一方で、Reactアンチパターンが積み重なると、どこを直せばよいかわからない、バグが入りやすい、レビューに時間がかかる、という状態になります。つまり、Reactの保守性はコード量ではなく、責務の分け方と状態設計で決まります。
Reactアンチパターン10選の一覧
まず、この記事で扱うReactアンチパターンを一覧で整理します。
| 番号 | アンチパターン | 起こる問題 |
|---|---|---|
| 1 | 1コンポーネントに全部書く | 修正箇所が分かりにくい |
| 2 | Props Drillingを放置する | 値の流れが追いにくい |
| 3 | 計算できる値をstateにする | 管理対象が増える |
| 4 | useEffectで状態同期する | 再レンダリングが増える |
| 5 | 親コンポーネントにstateを集めすぎる | 変更の影響範囲が広がる |
| 6 | コンポーネントの責務が曖昧 | 保守性が下がる |
| 7 | keyにindexを使う | 表示バグが起きやすい |
| 8 | React.memoを乱用する | コードが複雑になる |
| 9 | useCallbackを目的なく使う | 可読性が下がる |
| 10 | API取得・変換・表示を混ぜる | テストや修正が難しくなる |
ここからは、それぞれのReactアンチパターンについて、悪い例、なぜ問題なのか、改善の考え方を順番に見ていきます。
Reactアンチパターン1:1コンポーネントに全部書く
まず、React初心者が最もやりがちなアンチパターンは、1つのコンポーネントにすべての処理を書くことです。
例えば、最初は小さい画面でも、機能追加を続けるうちに、state管理、API取得、バリデーション、一覧表示、モーダル制御が1ファイルに集まっていきます。
悪い例:巨大なUserPage
function UserPage() {
// state管理
// API取得
// バリデーション
// テーブル表示
// モーダル管理
// ボタン処理
// 500行続く...
}
改善例:意味のある単位で分割する
function UserPage() {
return (
<>
<UserProfile />
<UserPostList />
<UserModal />
</>
);
}
一方で、コンポーネントを意味のある単位で分けると、どこを修正すればよいかが明確になります。ただし、細かく分けすぎると逆に追いにくくなるため、「表示の意味」「変更理由」「責務」を基準に分割します。
Reactアンチパターン2:Props Drillingを放置する
次に、Props Drillingとは、親から子へ、さらにその子へ、さらにその子へとpropsを渡し続けることです。
Props Drillingのイメージ
App ↓ Layout ↓ Page ↓ UserSection ↓ UserCard
例えば、途中のLayoutやPageがその値を使っていない場合、propsを渡すためだけのコードが増えます。その結果、値の流れが追いにくくなります。
改善するときは、まず本当に深く渡す必要があるかを確認します。ログインユーザー、テーマ設定、言語設定のように広い範囲で使う値はContextや状態管理ライブラリを検討します。一方で、一部画面だけの入力値までグローバル化する必要はありません。
なお、React公式のSharing State Between Componentsでも、stateごとに「どのコンポーネントが所有するか」を考えることが説明されています。
Reactアンチパターン3:計算できる値をstateにする
また、計算すれば分かる値までstateにするのも、よくあるReactアンチパターンです。
なぜなら、stateが増えるほど、更新タイミング、初期値、同期漏れを考える必要があるからです。そのため、propsや他のstateから計算できる値は、基本的にその場で計算します。
悪い例:fullNameをstateにする
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
改善例:レンダリング中に計算する
const fullName = `${firstName} ${lastName}`;
| 避けたい例 | 改善例 |
|---|---|
| フルネームをstateにする | 文字列結合で作る |
| 件数をstateにする | array.lengthで計算する |
| 合計金額をstateにする | reduceで計算する |
| 絞り込み結果をstateにする | filterで計算する |
React公式のYou Might Not Need an Effectでも、表示のために変換できるデータはEffectではなくレンダリング中に計算する考え方が紹介されています。
Reactアンチパターン4:useEffectで状態同期する
一方で、useEffectは便利ですが、何でも書く場所ではありません。特に、state同士を同期するためだけのuseEffectは要注意です。
悪い例:items.lengthをuseEffectで同期する
const [count, setCount] = useState(0);
useEffect(() => {
setCount(items.length);
}, [items]);
改善例:必要なときに計算する
const count = items.length;
状態同期useEffectの問題
items変更 ↓ 再レンダリング ↓ useEffect実行 ↓ setCount ↓ 再レンダリング
つまり、useEffectはReactの外側にあるものと同期するための仕組みです。例えば、ブラウザAPI、イベント登録、タイマー、外部システムとの接続などです。単なる計算や表示用データの変換なら、Effectを使わない方が読みやすくなります。
Reactアンチパターン5:親コンポーネントにstateを集めすぎる
次に、親コンポーネントにstateを集めすぎると、画面全体の責務が大きくなります。
もちろん、複数の子コンポーネントで共有するstateは親に置く必要があります。しかし、検索フォームだけで使う値、モーダルだけで使う開閉状態まで親に集めると、親コンポーネントが読みにくくなります。
| State | 置き場所の考え方 |
|---|---|
| 検索キーワード | SearchBoxまたは検索条件を扱う範囲 |
| モーダル開閉 | Modalを管理するコンポーネント周辺 |
| ページ番号 | Paginationや一覧の制御範囲 |
| ログインユーザー | アプリ全体で使うならContextなどを検討 |
基本は、stateを使う場所に近づけます。ただし、複数のコンポーネントで同じ値を同期したい場合は、共通の親へ持ち上げます。このバランスがReactの状態設計では重要です。
Reactアンチパターン6:コンポーネントの責務が曖昧
さらに、コンポーネントの名前と中身が一致していないコードも危険です。
悪い例:UserCardが表示以外も担当する
function UserCard() {
// API取得
// バリデーション
// モーダル管理
// 表示
}
改善例:取得と表示を分ける
function UserPage() {
const users = useUsers();
return <UserList users={users} />;
}
そのため、コンポーネントは「何を表示するか」、Custom Hookは「どう取得・管理するか」、純粋関数は「どう変換するか」を担当させると、コードの読み手が役割を理解しやすくなります。
Reactアンチパターン7:keyにindexを使う
また、リスト表示でkeyにindexを使うのも、Reactでよく見るアンチパターンです。
悪い例:indexをkeyにする
users.map((user, index) => (
<UserCard key={index} user={user} />
));
改善例:一意なIDをkeyにする
users.map((user) => (
<UserCard key={user.id} user={user} />
));
React公式のRendering Listsでは、keyは配列内の要素を識別するために必要だと説明されています。並び替え、追加、削除があるリストでは、indexではなくデータが持つ一意なIDを使うのが基本です。
Reactアンチパターン8:React.memoを乱用する
次に、React.memoはpropsが変わっていないときに再レンダリングをスキップするための機能です。ただし、すべてのコンポーネントに付ければ速くなるわけではありません。
悪い例:何も考えずにmemo化する
export default React.memo(UserCard);
そのため、React.memoを乱用すると、「なぜmemo化しているのか」がわからないコードになります。また、propsに毎回新しいオブジェクトや関数を渡している場合、期待した効果が出ないこともあります。
React公式のmemoでも、propsが変わらないときに再レンダリングをスキップする仕組みとして説明されています。まずは状態の置き場所やコンポーネント分割を見直し、それでも描画が重い場合に使うのが現実的です。
Reactアンチパターン9:useCallbackを目的なく使う
同じように、useCallbackも誤解されやすいHookです。useCallbackは関数そのものを速くするHookではありません。関数の参照を安定させるためのHookです。
悪い例:すべての関数をuseCallbackで包む
const handleClick = useCallback(() => {
console.log("click");
}, []);
例えば、この関数をmemo化した子コンポーネントに渡していないなら、useCallbackの効果は限定的です。むしろ依存配列を考える手間が増え、可読性が下がることがあります。
useCallbackが効きやすい場面
親が再レンダリング ↓ 関数が再生成される ↓ memo化した子に新しいpropsとして渡る ↓ 子が再レンダリングされる ↓ useCallbackで参照を安定させる
つまり、useCallbackはReact.memoとセットで意味を持つことが多いです。公式のuseCallbackも、関数定義をキャッシュするHookとして説明されています。
Reactアンチパターン10:API取得・変換・表示を混ぜる
最後に、実務で特に保守性を下げやすいReactアンチパターンが、API取得、データ変換、表示を1つのコンポーネントに混ぜることです。
悪い例:取得、変換、表示が混ざる
function UserPage() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetchUsers()
.then(users => users.map(user => ({
id: user.id,
name: `${user.firstName} ${user.lastName}`,
})))
.then(setUsers);
}, []);
return (
<div>
{users.map(user => (
<p key={user.id}>{user.name}</p>
))}
</div>
);
}
改善例:取得、変換、表示を分ける
function UserPage() {
const users = useUsers();
return <UserList users={users} />;
}
データ取得はCustom Hook、データ変換は関数、表示はComponentに分けると、変更理由が分かれます。その結果、テストしやすく、レビューしやすく、引き継ぎやすいコードになります。
Reactアンチパターンを見つける危険サイン
なお、次のような状態になっていたら、React設計を見直すタイミングです。
| 危険サイン | 起きている可能性 | 要注意度 |
|---|---|---|
| useEffectが5個以上ある | 状態設計が崩れている | 高 |
| stateが20個以上ある | 責務が大きすぎる | 高 |
| コンポーネントが500行を超える | 分割不足 | 高 |
| propsが10個以上ある | Props Drillingの可能性 | 中 |
| React.memoだらけ | 根本原因を隠している | 中 |
| eslint-disableが多い | 依存関係をごまかしている | 高 |
もちろん、これらの数字だけで悪い設計だと決めつける必要はありません。ただし、レビューや改修で何度も迷うなら、コンポーネント分割、stateの置き場所、useEffectの必要性を見直す価値があります。
良いReact設計に近づけるチェックリスト
- このコンポーネントは1つの責務に集中しているか?
- 計算できる値をstateにしていないか?
- useEffectで不要な状態同期をしていないか?
- propsを深く渡しすぎていないか?
- React.memoやuseCallbackを目的なく使っていないか?
- API取得、データ変換、表示が混ざっていないか?
つまり、良いReact設計とは、特別なテクニックを詰め込むことではありません。どこに何を書くかが明確で、変更理由ごとにコードが分かれている状態です。
まとめ:Reactアンチパターンを避けて変更しやすいコードを書く
今回は、保守性が下がるReactアンチパターンを10個紹介しました。
| 避けたいこと | 意識したいこと |
|---|---|
| 1コンポーネントに全部書く | 意味のある単位で分割する |
| 計算値をstateにする | 必要な時に計算する |
| useEffectを乱用する | 本当に副作用か考える |
| React.memoを過信する | まず設計を見直す |
| API取得と表示を混ぜる | 責務を分離する |
Reactは「動かす」よりも「変更しやすく作る」方が難しいです。実務では、新規開発よりも保守・改修の時間が長くなることも多いため、保守性を意識した設計が重要になります。
React・Next.jsの実務スキルを伸ばしたい方は、まず「どこに何を書くか」を整理するところから始めてみてください。
一度カジュアル面談をしませんか?
株式会社bluenaは「高還元」と「伴走支援」を両立したSES企業です。単価の81〜86%を還元する報酬体系と、専任サポーターによる隔週1on1で、エンジニアが納得できるキャリアを実現します。
React・Next.jsの実務経験をどう伸ばすか、今のスキルでどんな案件を選ぶべきか。まとまっていなくてもOKです。まずは現在地を聞かせてください。
カジュアル面談ですので、お気軽にお聞かせください。





