Advanced
React Nativeアプリケーションでの状態管理のベストプラクティスについて説明します。Context API、Redux、Zustandなどの状態管理ソリューションを比較し、適切な選択方法を学びます。
React Native での状態管理
React Native アプリケーションの開発において、状態管理は重要な要素の一つです。適切な状態管理により、アプリの保守性と拡張性を向上させることができます。
状態管理とは
状態管理とは、アプリケーション内のデータの流れと変更を管理する仕組みです。React Native では、コンポーネント間でデータを共有し、状態の変更を効率的に処理する必要があります。
状態管理の種類
1. ローカル状態(Local State)
コンポーネント内でのみ使用される状態です。useStateフックを使用して管理します。
import React, { useState } from "react";
import { View, Text, Button } from "react-native";
const Counter = () => {
const [count, setCount] = useState(0);
return (
<View>
<Text>カウント: {count}</Text>
<Button title="増加" onPress={() => setCount(count + 1)} />
<Button title="減少" onPress={() => setCount(count - 1)} />
</View>
);
};
2. グローバル状態(Global State)
複数のコンポーネント間で共有される状態です。主に以下の方法があります:
- Context API
- Redux
- Zustand
- Jotai
Context API を使用した状態管理
Context API は、React に組み込まれた状態管理ソリューションです。
基本的な実装
// contexts/AppContext.js
import React, { createContext, useContext, useReducer } from "react";
const AppContext = createContext();
const initialState = {
user: null,
theme: "light",
notifications: [],
};
const appReducer = (state, action) => {
switch (action.type) {
case "SET_USER":
return { ...state, user: action.payload };
case "SET_THEME":
return { ...state, theme: action.payload };
case "ADD_NOTIFICATION":
return {
...state,
notifications: [...state.notifications, action.payload],
};
default:
return state;
}
};
export const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};
export const useAppContext = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error("useAppContext must be used within AppProvider");
}
return context;
};
使用例
// components/UserProfile.js
import React from "react";
import { View, Text, Button } from "react-native";
import { useAppContext } from "../contexts/AppContext";
const UserProfile = () => {
const { state, dispatch } = useAppContext();
const handleLogin = () => {
dispatch({
type: "SET_USER",
payload: { name: "John Doe", email: "john@example.com" },
});
};
const handleLogout = () => {
dispatch({ type: "SET_USER", payload: null });
};
return (
<View>
{state.user ? (
<View>
<Text>ようこそ、{state.user.name}さん</Text>
<Button title="ログアウト" onPress={handleLogout} />
</View>
) : (
<Button title="ログイン" onPress={handleLogin} />
)}
</View>
);
};
Redux を使用した状態管理
Redux は、予測可能な状態管理ライブラリです。大規模なアプリケーションに適しています。
インストール
npm install @reduxjs/toolkit react-redux
基本的な実装
// store/index.js
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./slices/userSlice";
import themeReducer from "./slices/themeSlice";
export const store = configureStore({
reducer: {
user: userReducer,
theme: themeReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// store/slices/userSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
user: null,
isLoading: false,
error: null,
};
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
setUser: (state, action) => {
state.user = action.payload;
},
setLoading: (state, action) => {
state.isLoading = action.payload;
},
setError: (state, action) => {
state.error = action.payload;
},
},
});
export const { setUser, setLoading, setError } = userSlice.actions;
export default userSlice.reducer;
使用例
// components/UserProfile.js
import React from "react";
import { View, Text, Button } from "react-native";
import { useSelector, useDispatch } from "react-redux";
import { setUser } from "../store/slices/userSlice";
const UserProfile = () => {
const user = useSelector((state) => state.user.user);
const dispatch = useDispatch();
const handleLogin = () => {
dispatch(
setUser({
name: "John Doe",
email: "john@example.com",
})
);
};
return (
<View>
{user ? (
<Text>ようこそ、{user.name}さん</Text>
) : (
<Button title="ログイン" onPress={handleLogin} />
)}
</View>
);
};
Zustand を使用した状態管理
Zustand は、軽量でシンプルな状態管理ライブラリです。
インストール
npm install zustand
基本的な実装
// store/userStore.js
import { create } from "zustand";
const useUserStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
clearUser: () => set({ user: null }),
}));
export default useUserStore;
使用例
// components/UserProfile.js
import React from "react";
import { View, Text, Button } from "react-native";
import useUserStore from "../store/userStore";
const UserProfile = () => {
const { user, setUser, clearUser } = useUserStore();
const handleLogin = () => {
setUser({ name: "John Doe", email: "john@example.com" });
};
return (
<View>
{user ? (
<View>
<Text>ようこそ、{user.name}さん</Text>
<Button title="ログアウト" onPress={clearUser} />
</View>
) : (
<Button title="ログイン" onPress={handleLogin} />
)}
</View>
);
};
状態管理の選択指針
プロジェクトの規模による選択
| 規模 | 推奨ソリューション | 理由 |
|---|---|---|
| 小規模 | useState + Context API | シンプルで学習コストが低い |
| 中規模 | Zustand | 軽量で使いやすい |
| 大規模 | Redux Toolkit | 強力な開発者ツールと予測可能性 |
パフォーマンスの考慮事項
- 不要な再レンダリングの回避
- メモ化の適切な使用
- 状態の正規化
// メモ化の例
import React, { memo, useMemo } from "react";
const ExpensiveComponent = memo(({ data }) => {
const processedData = useMemo(() => {
return data.map((item) => ({
...item,
processed: true,
}));
}, [data]);
return (
<View>
{processedData.map((item) => (
<Text key={item.id}>{item.name}</Text>
))}
</View>
);
});
ベストプラクティス
1. 状態の正規化
// 悪い例
const state = {
users: [
{ id: 1, name: "John", posts: [{ id: 1, title: "Post 1" }] },
{ id: 2, name: "Jane", posts: [{ id: 2, title: "Post 2" }] },
],
};
// 良い例
const state = {
users: {
1: { id: 1, name: "John" },
2: { id: 2, name: "Jane" },
},
posts: {
1: { id: 1, title: "Post 1", userId: 1 },
2: { id: 2, title: "Post 2", userId: 2 },
},
};
2. アクションの命名規則
// 一貫性のある命名
const actions = {
SET_USER: "SET_USER",
UPDATE_USER: "UPDATE_USER",
DELETE_USER: "DELETE_USER",
SET_LOADING: "SET_LOADING",
SET_ERROR: "SET_ERROR",
};
3. エラーハンドリング
const userSlice = createSlice({
name: "user",
initialState: {
user: null,
isLoading: false,
error: null,
},
reducers: {
fetchUserStart: (state) => {
state.isLoading = true;
state.error = null;
},
fetchUserSuccess: (state, action) => {
state.isLoading = false;
state.user = action.payload;
},
fetchUserFailure: (state, action) => {
state.isLoading = false;
state.error = action.payload;
},
},
});
まとめ
React Native での状態管理は、アプリケーションの複雑さに応じて適切なソリューションを選択することが重要です。
主要なポイント
- プロジェクトの規模に応じた選択
- パフォーマンスの最適化
- 一貫性のある実装
- 適切なエラーハンドリング
適切な状態管理により、保守性が高く、拡張しやすいアプリケーションを構築できます。継続的な学習と実践を通じて、最適な状態管理パターンを習得していきましょう。