🐈

Reactにおけるさまざまなstate の管理

に公開

はじめに

この記事ではReactにおける「state 更新ロジックをメンテナンスしやすい状態に保つ方法、そして離れたコンポーネント間で state を共有する方法」について理解を深めたいと思います。

では初めにstate ロジックをリデューサに抽出する方法について触れていきたいと思います。

リデューサ関数とは何か

リデューサ関数は、状態管理において新しい状態を生成するための関数です。主にReactアプリケーションで使用され、状態の変更を処理する役割を果たします。リデューサは、前の状態とアクションを受け取り、新しい状態を返す純粋な関数です。

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

useState から useReducer にリファクタリングする方法

useReducerフックは、Reactで提供されている状態管理の仕組みで、useStateからuseReducerにリファクタリングする方法は、状態がより複雑な場合や状態変更のロジックが多くなった場合に有用です。useReducerはリデューサ関数を使用し、アクションをディスパッチして状態を更新します。

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

const [state, dispatch] = useReducer(reducer, initialState);

リデューサを適切に記述するためのポイント

  • リデューサは純粋な関数であるべきです。同じ入力に対して同じ出力を返すことが期待されます。

  • ステートオブジェクトは変更せずに新しいオブジェクトを生成して返すようにします。

  • スイッチステートメントを使用してアクションの型に基づいてステートを変更するロジックを実装します。

  • リデューサ内で副作用を起こさないようにし、非同期操作などを行わないようにします。

  • アクションの型やプロパティの設計に注意し、分かりやすい名前を使用します。

次にコンポーネント間で state を共有する方法について触れていきます。

プロップス (Props) を使用する

親コンポーネントから子コンポーネントへデータを渡すためにプロップスを使用します。これにより、親コンポーネントの状態を子コンポーネントに渡して共有できます。

// 親コンポーネント
function Parent() {
  const [count, setCount] = useState(0);
  return <Child count={count} />;
}

// 子コンポーネント
function Child(props) {
  return <p>Count: {props.count}</p>;
}

コンテキスト (Context) を使用する

コンテキストを活用して、コンポーネントツリー全体で状態を共有できます。これは多くのコンポーネントで同じデータにアクセスが必要な場合に便利です。

// コンテキストを作成
const MyContext = React.createContext();

// 親コンポーネント
function Parent() {
  const [count, setCount] = useState(0);
  return (
    <MyContext.Provider value={count}>
      <Child />
    </MyContext.Provider>
  );
}

// 子コンポーネント
function Child() {
  const count = useContext(MyContext);
  return <p>Count: {count}</p>;
}

それ以外にも状態管理ライブラリ(例: Redux)を使用してアプリケーション全体で状態を一元管理することもできます。

制御された (Controlled) コンポーネントと非制御 (Uncontrolled) コンポーネント

制御された (Controlled) コンポーネント

制御されたコンポーネントは、Reactの状態によってコンポーネントの動作を制御する方法です。通常、フォーム要素(input、select、textareaなど)に関連して使われます。制御されたコンポーネントでは、コンポーネントの状態をReactのステートに結びつけ、ステートの変更に応じてUIが更新されます。

function ControlledComponent() {
  const [inputValue, setInputValue] = useState('');

  const handleInputChange = (e) => {
    setInputValue(e.target.value);
  };

  return (
    <input
      type="text"
      value={inputValue}
      onChange={handleInputChange}
    />
  );
}

input要素の値(value)はinputValueステートによって制御されています。コンポーネントの状態とUI要素の値が同期しており、ユーザーの入力に応じてinputValueが更新されます。

非制御 (Uncontrolled) コンポーネント

非制御コンポーネントは、ReactのステートではなくDOM要素自体によって管理されるコンポーネントです。通常、リファクタリングや外部ライブラリとの統合時に使われます。非制御コンポーネントでは、Reactの状態に結びつけずに、直接DOM要素を操作します。

function UncontrolledComponent() {
  let inputRef = useRef(null);

  const handleButtonClick = () => {
    console.log('Input value:', inputRef.current.value);
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleButtonClick}>Get Value</button>
    </div>
  );
}

refを使用してinput要素を直接参照しています。Reactのステートによっては値が制御されず、ボタンクリック時に直接DOM要素から値を取得します。

Discussion