ReactのuseStateで複数状態を管理する最適な分割戦略を徹底解説

ReactでuseStateを利用する際、状態管理の方法がアプリケーションの保守性とパフォーマンスに大きな影響を与えます。特に、複数の状態を同時に管理する場合、useStateをどのように活用するかが重要なポイントとなります。状態を一つにまとめるべきか、それとも分割して管理すべきか、多くの開発者が直面するこの課題に対し、本記事では分割戦略を中心に解説します。効率的な状態管理の基礎から応用までを理解し、Reactアプリケーションをよりスケーラブルでメンテナンスしやすいものにする方法を探っていきましょう。

目次

ReactのuseState基本概念


useStateは、Reactの関数コンポーネントで状態管理を行うためのフックです。Reactの状態は、ユーザーの操作やアプリケーションの動作に応じて動的に変化するデータを管理します。useStateを使うことで、Reactの再レンダリング機能を活用し、UIを動的に更新することが可能です。

useStateの基本的な使い方


useStateは、状態とその更新関数をペアで返す構造です。以下のように使います:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // 状態と初期値を定義

  const increment = () => setCount(count + 1); // 状態更新関数で状態を変更

  return (
    <div>
      <p>現在のカウント: {count}</p>
      <button onClick={increment}>カウントアップ</button>
    </div>
  );
}

初期値の設定


useStateに渡した初期値は、コンポーネントが初期化されるときに一度だけ使用されます。初期値には以下のようなデータ型を使用できます:

  • プリミティブ型(例: 0, '', true
  • 配列(例: []
  • オブジェクト(例: {}

useStateの動作原理


useStateの動作は以下のような特徴があります:

  • 状態が変更されると、Reactが再レンダリングをトリガーします。
  • 更新関数を呼び出すと、状態は新しい値に置き換わります。
  • 再レンダリング時にはuseStateの状態が保持され、初期値は再度評価されません。

複数の状態管理


1つのコンポーネントでuseStateを複数回使用することができます。たとえば、以下のコードでは、counttextという2つの状態を管理しています:

function Example() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  return (
    <div>
      <p>Count: {count}</p>
      <p>Text: {text}</p>
    </div>
  );
}

useStateの基本を理解することで、より複雑な状態管理や最適化へと進む準備が整います。次のセクションでは、複数の状態を一つにまとめた管理方法を解説します。

複数状態を一つのuseStateで管理する場合

useStateは、一つの状態だけでなく、オブジェクトや配列を使うことで複数の状態をまとめて管理することができます。この方法は便利ですが、同時にいくつかの注意点もあります。

オブジェクトを用いた状態管理


複数の関連する状態を一つのオブジェクトでまとめて管理する方法は、以下のように実装できます:

function UserForm() {
  const [formState, setFormState] = useState({
    name: '',
    email: '',
    age: ''
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormState({
      ...formState, // 既存の状態を展開
      [name]: value // 変更されたフィールドを上書き
    });
  };

  return (
    <form>
      <input
        name="name"
        value={formState.name}
        onChange={handleChange}
        placeholder="名前"
      />
      <input
        name="email"
        value={formState.email}
        onChange={handleChange}
        placeholder="メールアドレス"
      />
      <input
        name="age"
        value={formState.age}
        onChange={handleChange}
        placeholder="年齢"
      />
    </form>
  );
}

メリット

  • 状態がまとまるため、読みやすく管理しやすい。
  • フォームや関連するデータを扱う際に便利。

注意点

  • 状態の部分更新には、スプレッド演算子やObject.assignを使う必要があり、記述が冗長になる可能性がある。
  • 状態が複雑になると、コードが読みにくくなり、管理が困難になる場合がある。

配列を用いた状態管理


オブジェクトではなく配列を使うことで、順序や同じ種類のデータをまとめることも可能です:

function TodoList() {
  const [todos, setTodos] = useState([]);

  const addTodo = (newTodo) => {
    setTodos([...todos, newTodo]); // 配列を新しい要素で更新
  };

  return (
    <div>
      <button onClick={() => addTodo({ id: Date.now(), task: '新しいタスク' })}>
        タスク追加
      </button>
      {todos.map((todo) => (
        <p key={todo.id}>{todo.task}</p>
      ))}
    </div>
  );
}

メリット

  • 同種のデータをリスト形式で管理しやすい。
  • mapfilterを利用して柔軟に操作できる。

注意点

  • 配列操作におけるパフォーマンスを考慮する必要がある場合がある。
  • 複雑な変更(特定の要素の削除や編集など)には追加のロジックが必要。

まとめて管理する場合の課題

  • レンダリングの効率:useStateにまとめすぎると、状態の一部変更でも全体が再レンダリングされる可能性がある。
  • スケーラビリティ:アプリケーションが大規模化すると、単一の状態にまとめる方法は柔軟性を欠くことがあります。

このように、オブジェクトや配列を利用して複数の状態をまとめる方法には利点と課題があります。次のセクションでは、これらの課題に対応するための状態分割戦略について解説します。

状態の分割戦略の必要性

Reactで複数の状態を管理する際、すべてを一つのuseStateにまとめると、コードの複雑化や保守性の低下につながる可能性があります。これを回避するために、状態を適切に分割することが重要です。

状態分割のメリット


状態分割を行うことで、以下のような利点があります:

1. コードの可読性向上


状態を小さな単位に分割することで、コードがモジュール化され、可読性が向上します。たとえば、状態が複数の用途に分かれている場合、それぞれ独立して管理する方が直感的です。

2. 再レンダリングの効率化


useStateで一つにまとめられた状態が更新されると、その状態を利用するすべてのコンポーネントが再レンダリングされます。一方、状態を分割することで、不要なレンダリングを防ぐことができます。

3. バグの発生リスクの低減


複雑なオブジェクトを扱う場合、スプレッド演算子やマージ操作が必要になるため、意図しない変更が発生しやすくなります。分割することで、各状態を単純化し、ミスを防ぐことができます。

状態分割の適用例


次の例では、状態を分割することで管理を効率化しています。

分割しない場合


以下は、フォームデータを一つのオブジェクトで管理する例です:

const [formState, setFormState] = useState({
  name: '',
  email: '',
  password: ''
});

const handleChange = (e) => {
  const { name, value } = e.target;
  setFormState({ ...formState, [name]: value }); // 毎回スプレッド演算子を利用
};

この方法では、状態の一部変更でもオブジェクト全体を更新する必要があります。

分割した場合


以下のように分割すると、各状態を個別に管理できます:

const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleNameChange = (e) => setName(e.target.value);
const handleEmailChange = (e) => setEmail(e.target.value);
const handlePasswordChange = (e) => setPassword(e.target.value);

この方法では、それぞれの状態を独立して管理できるため、再レンダリングや更新が効率化されます。

状態分割のデメリットと対処法


分割戦略にも考慮すべき点があります:

1. 記述量の増加


状態を分割すると、useStateや更新関数の記述が増える場合があります。この問題には、カスタムフックを利用することで対応できます。

2. 状態の関係性の管理


分割された状態同士が強く関連している場合、それぞれを独立させると逆にコードが煩雑になることがあります。この場合、useReducerを利用して一括管理する方法が有効です。

分割戦略が必要なケース

  • 状態の更新頻度が異なる場合。
  • 状態が独立しており、他の状態と影響を与え合わない場合。
  • 再レンダリングのパフォーマンスが問題となる場合。

状態を分割することで、管理の効率が大幅に向上します。次のセクションでは、具体的な実践例を通じて分割戦略の効果を確認していきます。

状態分割の実践例

状態分割の考え方を実際のコードで理解するために、簡単な例を用いてその効果を見ていきましょう。ここでは、ToDoリストを管理するシナリオを考えます。

分割しない場合のコード


まず、すべての状態を1つのオブジェクトで管理する例を示します:

import React, { useState } from 'react';

function TodoApp() {
  const [state, setState] = useState({
    todos: [],
    newTodo: ''
  });

  const addTodo = () => {
    setState({
      ...state,
      todos: [...state.todos, state.newTodo],
      newTodo: ''
    });
  };

  const handleChange = (e) => {
    setState({
      ...state,
      newTodo: e.target.value
    });
  };

  return (
    <div>
      <input
        type="text"
        value={state.newTodo}
        onChange={handleChange}
        placeholder="新しいタスクを入力"
      />
      <button onClick={addTodo}>追加</button>
      <ul>
        {state.todos.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

export default TodoApp;

この方法の課題

  • 状態を更新するたびにスプレッド演算子を使用する必要があり、冗長です。
  • 状態が増えると管理が煩雑になり、意図しないエラーの原因となります。
  • todosnewTodoの変更に対して、常にオブジェクト全体が再レンダリングされます。

分割した場合のコード


次に、状態を分割して管理する例を示します:

import React, { useState } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [newTodo, setNewTodo] = useState('');

  const addTodo = () => {
    setTodos([...todos, newTodo]); // 配列だけを更新
    setNewTodo(''); // 入力フィールドをクリア
  };

  const handleChange = (e) => {
    setNewTodo(e.target.value); // 入力状態だけを更新
  };

  return (
    <div>
      <input
        type="text"
        value={newTodo}
        onChange={handleChange}
        placeholder="新しいタスクを入力"
      />
      <button onClick={addTodo}>追加</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

export default TodoApp;

分割した場合の利点

  • todosnewTodoが独立しているため、各状態の変更が他方に影響を与えません。
  • 再レンダリングの効率が向上し、必要な部分だけを更新できます。
  • 状態更新のコードが簡潔になり、可読性が向上します。

分割戦略の具体例


以下のシナリオで状態分割が有効です:

  • フォーム入力:複数のフィールド(名前、メールアドレスなど)をそれぞれ独立した状態として扱う。
  • 動的リスト管理:リストのデータと入力中のデータを分割して管理する。
  • タブやフィルタの状態:タブの選択状態やフィルタ条件を個別に管理することで、UIのレスポンスを向上させる。

状況に応じた分割の判断


状態を分割すべきかどうかは以下を基準に判断します:

  • 状態同士の関連性が強ければ、まとめた方が効率的。
  • 独立した処理が多い場合、分割することで複雑さを軽減できる。

このように、実際のユースケースで状態を分割することで、Reactアプリケーションのパフォーマンスと保守性を向上させることができます。次のセクションでは、状態管理におけるuseReducerとの比較を行い、それぞれの特性を深掘りします。

useReducerとの比較

Reactでは、状態管理を行う方法としてuseStateだけでなくuseReducerを利用する選択肢があります。それぞれの特性と適した場面を理解することで、適切なツールを選択しやすくなります。

useReducerの基本的な仕組み


useReducerは、状態管理のためのReducer関数と初期状態を受け取り、現在の状態とdispatch関数を返します。以下は、useReducerを使った簡単なカウンターの例です:

import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error('不明なアクションタイプ');
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>現在のカウント: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>増加</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>減少</button>
    </div>
  );
}

useReducerの特徴

  • 単一のReducer関数で複数の状態遷移を管理できる。
  • 状態遷移ロジックを明確に定義するため、状態の管理が複雑でも予測可能。
  • useStateよりも冗長なコードになる場合があるが、状態が複雑な場合には一貫性が向上する。

useStateとuseReducerの違い

特性useStateuseReducer
単純な状態管理適している冗長になりやすい
複雑な状態遷移適さない(スプレッド演算子が多用される)明確に定義可能で管理が容易
複数の状態管理個別のuseStateで管理可能状態遷移をまとめて管理可能
デバッグのしやすさ状態ごとに分散しているReducer関数で集中管理
学習コスト低い高い(Reduxに近い概念)

useReducerが適している場合

以下のような状況では、useReducerを選択するのが適切です:

1. 状態の遷移ロジックが複雑


例えば、状態が複数のアクションによって変化する場合、useReducerを使うとコードが整理されます:

function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return { items: [...state.items, action.payload] };
    case 'remove':
      return { items: state.items.filter(item => item.id !== action.payload) };
    default:
      return state;
  }
}

2. 状態管理がスケールする場合


状態が多岐にわたる場合、Reducer関数で一括して管理することで、拡張性が向上します。

3. 状態更新が予測可能であるべき場合


Reducer関数に状態遷移のルールを定義することで、状態管理に一貫性を持たせることができます。

useStateが適している場合

1. 単純な状態管理


たとえば、カウンターやフォーム入力のように、状態が単純な場合はuseStateが適しています。

2. 状態間の関連性が少ない場合


個別に管理することで、コードが簡潔になります。

適切な選択をするためのガイドライン

  • 状態が少なく単純であれば、useStateを使用。
  • 状態が多く複雑で、特に遷移ロジックが重要であれば、useReducerを使用。

useStateuseReducerは、どちらもReactで効果的な状態管理を実現するためのツールです。それぞれの特性を理解し、プロジェクトの要件に応じて適切に選択することで、効率的なアプリケーション開発が可能になります。次のセクションでは、状態管理の際によく見られるアンチパターンを紹介します。

状態管理のアンチパターン

Reactで状態管理を行う際、効率性や可読性を損なう実装は避けるべきです。これらのアンチパターンを理解し、最適な方法を選択することで、コードの保守性とパフォーマンスを向上させることができます。

アンチパターン1: 状態を1つのuseStateに詰め込みすぎる


複数の状態を1つのオブジェクトや配列でまとめて管理すると、更新時の複雑さが増します。

const [state, setState] = useState({
  name: '',
  email: '',
  age: '',
  todos: []
});

// フィールドの変更
const handleChange = (e) => {
  const { name, value } = e.target;
  setState({
    ...state,
    [name]: value
  });
};

なぜ問題なのか

  • スプレッド演算子を多用し、冗長なコードになる。
  • オブジェクト全体を更新するため、再レンダリングの負荷が高くなる。
  • 状態が増えると管理が煩雑になり、バグが生じやすい。

解決策


状態を分割して管理し、必要な部分だけを更新します:

const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState('');
const [todos, setTodos] = useState([]);

アンチパターン2: 必要以上にuseStateを多用する


逆に、関連性のある状態を細かく分割しすぎると、状態の追跡や変更が難しくなります。

const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');

なぜ問題なのか

  • 状態が分散し、更新ロジックが複雑になる。
  • 関連する状態をまとめて処理するのが困難になる。

解決策


関連する状態をオブジェクトとしてまとめて管理することで、コードが簡潔になります:

const [user, setUser] = useState({
  firstName: '',
  lastName: '',
  email: ''
});

アンチパターン3: 状態を深いネスト構造で管理する


状態が深くネストされたオブジェクトで管理されると、特定の値を変更するのに多くの処理が必要になります。

const [state, setState] = useState({
  user: {
    profile: {
      name: '',
      age: ''
    }
  }
});

// 名前を変更
const updateName = (newName) => {
  setState({
    ...state,
    user: {
      ...state.user,
      profile: {
        ...state.user.profile,
        name: newName
      }
    }
  });
};

なぜ問題なのか

  • スプレッド演算子が多用され、コードが冗長化する。
  • ミスが発生しやすく、保守性が低下する。

解決策


状態をフラット化し、ネストを避ける設計を目指します。また、複雑な状態にはuseReducerを活用します。

const [name, setName] = useState('');
const [age, setAge] = useState('');

アンチパターン4: 状態をコンポーネント間で直接共有する


状態を親から子、または兄弟コンポーネント間で直接渡そうとするのは避けるべきです。

function Parent() {
  const [state, setState] = useState('');

  return (
    <Child state={state} setState={setState} />
  );
}

function Child({ state, setState }) {
  // 状態を直接操作
  setState('新しい値');
}

なぜ問題なのか

  • 状態が複数のコンポーネントで頻繁に変更されると、追跡が困難になる。
  • 状態管理の責任が不明瞭になる。

解決策


状態をコンテキストや状態管理ライブラリ(ReduxやZustandなど)で管理するか、親コンポーネントが責任を持つようにします。

アンチパターン5: 効率を無視した不必要な再レンダリング


すべての状態を親コンポーネントで管理し、子コンポーネントに渡すと、不要な再レンダリングが発生します。

function Parent() {
  const [state, setState] = useState('');

  return (
    <Child state={state} />
  );
}

なぜ問題なのか

  • 親コンポーネントの状態が変更されるたびに、子コンポーネントも再レンダリングされる。

解決策


メモ化(React.memouseCallback)を活用して、再レンダリングを最適化します。

const Child = React.memo(({ state }) => {
  return <p>{state}</p>;
});

まとめ


状態管理のアンチパターンを回避することで、コードのパフォーマンスと可読性を向上させることができます。状態を適切に設計し、必要なツール(useState、useReducer、コンテキストなど)を使い分けることが、効率的なReact開発の鍵です。次のセクションでは、状態管理の最適化とパフォーマンス向上について解説します。

コードの最適化とパフォーマンス向上

Reactアプリケーションでは、状態管理が適切に行われていないと、不要な再レンダリングが発生し、パフォーマンスに悪影響を及ぼします。このセクションでは、状態管理における最適化手法と、それによるパフォーマンス向上の方法を解説します。

不要な再レンダリングを防ぐ

Reactでは、状態の変更が発生すると関連するコンポーネントが再レンダリングされます。この挙動を最適化する方法を見ていきます。

1. React.memoを活用する


React.memoを使用すると、親コンポーネントが再レンダリングされても、子コンポーネントの再レンダリングを防げます:

const Child = React.memo(({ count }) => {
  console.log('Childがレンダリングされました');
  return <p>Count: {count}</p>;
});

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <Child count={count} />
    </div>
  );
}

2. useCallbackで関数のメモ化


関数が毎回新しく生成されると、それを依存としているコンポーネントが再レンダリングされます。useCallbackを使って関数をメモ化することで、この問題を防げます:

function Parent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const increment = useCallback(() => {
    setCount((prev) => prev + 1);
  }, []);

  return (
    <div>
      <button onClick={increment}>Increment</button>
      <input value={text} onChange={(e) => setText(e.target.value)} />
    </div>
  );
}

3. useMemoで値の計算を最適化


計算コストが高い処理をメモ化することで、無駄な計算を減らすことができます:

const expensiveCalculation = (num) => {
  console.log('計算中...');
  return num * 2;
};

function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const doubleCount = useMemo(() => expensiveCalculation(count), [count]);

  return (
    <div>
      <p>結果: {doubleCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <input value={text} onChange={(e) => setText(e.target.value)} />
    </div>
  );
}

状態のスコープを適切に管理する

1. 状態を必要な範囲に限定する


グローバルに管理する必要のない状態を親コンポーネントで保持すると、再レンダリングが広範囲に影響します。状態はできるだけローカルに保つようにしましょう。

function Parent() {
  return (
    <div>
      <Child />
    </div>
  );
}

function Child() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>Increment</button>
  );
}

2. コンテキストAPIの使用を最小限にする


コンテキストAPIを多用すると、不要なコンポーネントまで再レンダリングの影響を受けます。useContextを慎重に使用し、必要な部分だけにデータを供給する設計を心がけましょう。

状態の依存関係を明確にする

依存関係配列(dependencies array)を適切に管理することで、予期しない再レンダリングを防ぎます。

例: useEffectの依存関係


useEffectを使用する際は、依存関係配列を正確に設定しましょう:

useEffect(() => {
  console.log('countが変更されました');
}, [count]); // countが変更されたときのみ実行

パフォーマンス向上のまとめ

  • React.memo: 再レンダリングのコントロール。
  • useCallbackとuseMemo: 関数や計算結果をメモ化。
  • 状態のローカル化: 状態スコープを限定し、影響範囲を縮小。
  • 依存関係の適切な設定: 無駄な再レンダリングを防ぐ。

これらのテクニックを活用することで、Reactアプリケーションのパフォーマンスを向上させることができます。次のセクションでは、理解を深めるための演習問題と実際の応用例を紹介します。

演習問題と応用例

Reactでの状態管理をより深く理解し、実践に役立てるための演習問題と、具体的な応用例を紹介します。これにより、useStateや状態分割の効果的な活用方法を学べます。

演習問題

問題1: フォームの状態管理


以下の要件を満たすフォームを実装してください:

  • ユーザー名、メールアドレス、パスワードを入力できる。
  • 各入力フィールドの状態をuseStateで分割して管理する。
  • フォーム送信時に、入力されたデータをコンソールに出力する。

期待される挙動

function FormExample() {
  const [username, setUsername] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ username, email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
        placeholder="ユーザー名"
      />
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="メールアドレス"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="パスワード"
      />
      <button type="submit">送信</button>
    </form>
  );
}

問題2: ToDoリストの状態管理


以下の要件を満たすToDoリストを作成してください:

  • タスクを入力し、リストに追加できる。
  • 各タスクには削除ボタンがあり、クリックすると削除される。
  • タスクの状態を配列としてuseStateで管理する。

期待される挙動

function TodoList() {
  const [tasks, setTasks] = useState([]);
  const [newTask, setNewTask] = useState('');

  const addTask = () => {
    setTasks([...tasks, { id: Date.now(), task: newTask }]);
    setNewTask('');
  };

  const removeTask = (id) => {
    setTasks(tasks.filter((task) => task.id !== id));
  };

  return (
    <div>
      <input
        type="text"
        value={newTask}
        onChange={(e) => setNewTask(e.target.value)}
        placeholder="新しいタスクを入力"
      />
      <button onClick={addTask}>追加</button>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>
            {task.task}
            <button onClick={() => removeTask(task.id)}>削除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

問題3: パフォーマンスの最適化


以下のコードを改善し、React.memoとuseCallbackを使って再レンダリングを最適化してください:

function Parent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <button onClick={increment}>Increment</button>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <Child count={count} />
    </div>
  );
}

function Child({ count }) {
  console.log('Childがレンダリングされました');
  return <p>Count: {count}</p>;
}

応用例

例1: モーダル管理


複数のモーダル(例: サインインモーダル、詳細モーダル)を管理するシナリオを考えます。状態分割とuseReducerを活用して、モーダルの表示状態を効率的に管理できます。

function reducer(state, action) {
  switch (action.type) {
    case 'OPEN':
      return { ...state, [action.modal]: true };
    case 'CLOSE':
      return { ...state, [action.modal]: false };
    default:
      return state;
  }
}

function ModalManager() {
  const [state, dispatch] = useReducer(reducer, { signin: false, detail: false });

  return (
    <div>
      <button onClick={() => dispatch({ type: 'OPEN', modal: 'signin' })}>サインインモーダルを開く</button>
      <button onClick={() => dispatch({ type: 'OPEN', modal: 'detail' })}>詳細モーダルを開く</button>

      {state.signin && <div>サインインモーダル</div>}
      {state.detail && <div>詳細モーダル</div>}
    </div>
  );
}

例2: ダークモードの切り替え


グローバル状態としてダークモードの設定を管理し、アプリ全体でテーマを切り替えます。

function ThemeSwitcher() {
  const [isDarkMode, setDarkMode] = useState(false);

  const toggleTheme = () => setDarkMode(!isDarkMode);

  return (
    <div style={{ backgroundColor: isDarkMode ? 'black' : 'white', color: isDarkMode ? 'white' : 'black' }}>
      <button onClick={toggleTheme}>テーマを切り替える</button>
    </div>
  );
}

これらの演習問題や応用例を通じて、Reactの状態管理における知識をさらに深めることができます。次のセクションでは、本記事の内容をまとめます。

まとめ

本記事では、ReactのuseStateを用いた複数の状態管理について、基本概念から応用例まで幅広く解説しました。状態を一つにまとめる方法と分割する戦略の違い、そして分割の必要性について具体例を交えながら理解を深めました。

さらに、useReducerとの比較や、状態管理におけるアンチパターン、パフォーマンス最適化のテクニックも取り上げました。React.memoやuseCallback、useMemoの活用方法を学ぶことで、アプリケーションの効率を大幅に向上させる方法もご紹介しました。

最後に、演習問題と応用例を通じて、学んだ知識を実際のプロジェクトに応用する方法を学びました。適切な状態管理を行うことで、Reactアプリケーションの保守性とパフォーマンスを最大化できます。

これらの知識を活用して、スケーラブルで効率的なReactアプリケーションを構築してください!

コメント

コメントする

目次