📖

Reduxはなぜ必要?(TypeScriptサンプルコード付)

2025/02/08に公開

はじめに

これからReactNativeエンジニアとして働くことになり、初めてReduxの触れることになりました。
そのため、TypeScriptのサンプルコードを実行しながら、概念から実装までを体系的に学んでいこうと思います。
実際にコードを書きながら、Reduxの仕組みの活用方法を深く理解してみましょう!

Reduxとは?

Reactのコンポーネントたちの状態のロジックやグローバル状態管理ができるライブラリです。
Reactでコンポーネント基盤開発をする時、親コンポーネントに対して子コンポーネントが多くなるほどpropsの伝達も増えることで、全コンポーネントの再レンダーリングが発生してしまい、パフォーマンスの低下につながる可能性が高くなります。
このような問題点を解消するためにReduxを利用すると、グローバル状態管理をすると管理しやすくなります。

Propsで状態管理(サンプルコード)

最上位のコンポーネント

// App.tsx - propsを渡す非効率な方法
import React, { useState } from "react";
import UserProfile from "./components/UserProfile";

interface User {
  id: number;
  name: string;
  age: number;
  email: string;
  address: string;
  phone: string;
}

const App: React.FC = () => {
  const [user, setUser] = useState<User>({
    id: 1,
    name: "Jiyeon",
    age: 25,
    email: "Jiyeon@example.com",
    address: "Tokyo, Japan",
    phone: "090-1234-5678",
  });

  const updateUser = (newUserData: Partial<User>) => {
    setUser((prevUser) => ({ ...prevUser, ...newUserData }));
  };

  return (
    <div>
      <h1>ユーザー情報</h1>
      {/* propsを複数のコンポーネントに渡さなければならない */}
      <UserProfile user={user} updateUser={updateUser} />
    </div>
  );
};

export default App;

中間コンポーネント

// components/UserProfile.tsx - propsを受け取り、さらに下位コンポーネントに渡す
import React from "react";
import UserDetail from "./UserDetail";

interface User {
  id: number;
  name: string;
  age: number;
  email: string;
  address: string;
  phone: string;
}

interface UserProfileProps {
  user: User;
  updateUser: (newUserData: Partial<User>) => void;
}

const UserProfile: React.FC<UserProfileProps> = ({ user, updateUser }) => {
  return (
    <div>
      <h2>プロフィール</h2>
      {/* propsをそのままUserDetailコンポーネントに渡す */}
      <UserDetail user={user} updateUser={updateUser} />
    </div>
  );
};

export default UserProfile;

最下層のコンポーネント

// components/UserDetail.tsx - 最終的にpropsを使用
import React from "react";

interface User {
  id: number;
  name: string;
  age: number;
  email: string;
  address: string;
  phone: string;
}

interface UserDetailProps {
  user: User;
  updateUser: (newUserData: Partial<User>) => void;
}

const UserDetail: React.FC<UserDetailProps> = ({ user, updateUser }) => {
  const changeUserName = () => {
    updateUser({ name: "Updated Name" });
  };

  return (
    <div>
      <p>名前: {user.name}</p>
      <p>年齢: {user.age}</p>
      <p>メール: {user.email}</p>
      <p>住所: {user.address}</p>
      <p>電話番号: {user.phone}</p>
      <button onClick={changeUserName} className="mt-2 p-2 bg-blue-500 text-white">
        名前を変更
      </button>
    </div>
  );
};

export default UserDetail;

Props方式の問題点

すべての親コンポーネントで props を追加する必要がある

  • 新しいコンポーネントが追加されるたびに、最上位のコンポーネント(App.tsx)からすべての親コンポーネントを修正する必要がある
  • コンポーネント間の結合度が高くなり、メンテナンスも難しくなりやすい

props の受け渡しが増えるとコードが複雑になる

  • props の受け渡しが多くなると、コードが長くなり、可読性が低下する

新しい状態フィールドを追加すると、すべてのコンポーネントを修正する必要がある

  • 例えば、user オブジェクトに nickname フィールドを追加するとしたら**App.tsxUserProfile.tsxUserDetail.tsxのすべてを修正する必要がある**

🔥Reduxを使って状態管理を簡単にしてみよう

上記のpropsを使った状態管理のサンプルコードをRedux Toolkitを使ってリファクタリングしてみましょう。
Reduxを使うことでpropsの受け渡しをなくし、どのコンポーネントからでも状態を簡単に取得できるようになります。

reduxの仕組み

イメージソース:freecodecamp

Redux は 以下の 5 つの要素 で構成されています。

要素 説明
View/UI ユーザーが操作する画面 (Reactコンポーネント)
Action 状態を変更するための命令 (イベントの発生)
Reducer Action に基づいて State を変更する関数
Store アプリケーション全体の状態を保持するオブジェクト
Dispatch ActionReducer に送る関数

Reduxのデータフロー

  1. ユーザーが UI で操作する (ボタンを押すなど)
  2. dispatch(Action) を発行し、アクションを発生させる
  3. ReducerAction の内容を元に State を変更する
  4. 変更後の StateStore に保存される
  5. Store の変更を subscribe している View/UI が検知し、画面が更新される

これにより、アプリケーション全体の状態が予測可能になり、管理しやすくなリます。
上記のフローを覚えながらコードをリファクタリングしてみましょう。

Reduxで状態管理(サンプルコード)

Redux のストアを作成

まず、createSliceを使ってReducerActionをまとめて作成します。

// store/userSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";

// ユーザー情報の型定義
interface User {
  id: number;
  name: string;
  age: number;
  email: string;
  address: string;
  phone: string;
}

// 初期状態
const initialState: User = {
  id: 1,
  name: "Jiyeon",
  age: 25,
  email: "Jiyeon@example.com",
  address: "Tokyo, Japan",
  phone: "090-1234-5678",
};

// Redux Slice の作成
const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    updateUser: (state, action: PayloadAction<Partial<User>>) => {
      return { ...state, ...action.payload };
    },
  },
});

// アクションとリデューサーのエクスポート
export const { updateUser } = userSlice.actions;
export default userSlice.reducer;

Redux ストアのセットアップ

configureStoreを使ってReduxストアを作成します。

// store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";

export const store = configureStore({
  reducer: {
    user: userReducer,
  },
});

// RootState と Dispatch の型定義
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Provider で Redux を適用

ReduxProviderでアプリ全体をラップします。これで全てのコンポーネントからReduxの状態を使用できるようになります!

// _app.tsx
import { Provider } from "react-redux";
import { store } from "../store";

function MyApp({ Component, pageProps }: any) {
  return (
    <Provider store={store}>
      <Component {...pageProps} />
    </Provider>
  );
}

export default MyApp;

propsの受け渡しなしでReduxの状態を取得

propsの受け渡しを完全に削除しました!子コンポーネントはReduxから直接状態を取得できます。

親コンポーネント

// App.tsx - propsを渡さずにReduxの状態を直接使用
import React from "react";
import UserProfile from "./components/UserProfile";
import UserDetail from "./components/UserDetail";

const App: React.FC = () => {
  return (
    <div>
      <h1>ユーザー情報</h1>
      <UserProfile />
      <UserDetail />
    </div>
  );
};

export default App;

子コンポーネント1

// components/UserProfile.tsx - Redux から直接状態を取得
import React from "react";
import { useSelector } from "react-redux";
import { RootState } from "../store";

const UserProfile: React.FC = () => {
  const user = useSelector((state: RootState) => state.user);

  return (
    <div>
      <h2>プロフィール</h2>
      <p>名前: {user.name}</p>
      <p>年齢: {user.age}</p>
      <p>メール: {user.email}</p>
    </div>
  );
};

export default UserProfile;

子コンポーネント2

// components/UserDetail.tsx - Redux の状態を直接使用し、更新も可能
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { RootState, updateUser } from "../store/userSlice";

const UserDetail: React.FC = () => {
  const user = useSelector((state: RootState) => state.user);
  const dispatch = useDispatch();

  const changeUserName = () => {
    dispatch(updateUser({ name: "Updated Name" }));
  };

  return (
    <div>
      <p>名前: {user.name}</p>
      <p>年齢: {user.age}</p>
      <p>メール: {user.email}</p>
      <p>住所: {user.address}</p>
      <p>電話番号: {user.phone}</p>
      <button onClick={changeUserName} className="mt-2 p-2 bg-blue-500 text-white">
        名前を変更
      </button>
    </div>
  );
};

export default UserDetail;

Reduxを使うことで、propsの受け渡しなしで、どのコンポーネントからでもStateを直接取得できるようになります!

まとめ

Redux を導入することで、以下のメリットがあります。

  • propsの受け渡しを削減し、コードの可読性を向上
  • アプリケーションの状態をグローバルに管理し、状態管理を簡潔に
  • どのコンポーネントからでもuseSelectorで状態を取得可能
  • useDispatchを利用して、状態を簡単に更新

Reduxを活用することで、Reactアプリの状態管理をスムーズにし、スケーラブルな開発が可能になりますので活用してみましょう 🚀

Discussion