状態管理とバックエンドAPIデータ連携
アプリが2〜3ページしかない場合、React組み込みのuseStateとpropsでデータを渡せます。しかしアプリが大きくなるにつれ:
- 「ホーム」にユーザーアバターを表示
- 「プロファイルページ」でアバターを変更可能
- 「サイドバー」にもアバター表示が必要
この時もpropsで階層的にデータを渡すと、有名な「Props Drilling(プロパティ地獄)」が発生します。ここで必要になるのが**グローバル状態管理(Global State Management)**です。
1. Reduxを捨て、Zustandへ!
以前はReact開発者にReduxが愛用されていました。しかしReduxの設定は極めて煩雑で、Action、Reducer、Dispatchを大量に書く必要があり、小規模プロジェクトには過剰でした。
近年、Zustand(ドイツ語で「状態」の意)が現代のReact/RN開発者に愛されるようになりました。超軽量で<Provider>でアプリ全体をラップする必要がなく、普通のHookと同じように簡単に書けます!
Zustandインストール:
npm install zustand
Storeの作成
プロジェクトにstore/useAuthStore.tsを作成:
import { create } from 'zustand';
// 1. 型定義
interface AuthState {
user: { name: string; avatar: string } | null;
isAuthenticated: boolean;
login: (userData: { name: string; avatar: string }) => void;
logout: () => void;
}
// 2. Store作成
export const useAuthStore = create<AuthState>((set) => ({
user: null,
isAuthenticated: false,
// 状態変更メソッド(Actions)定義
login: (userData) => set({ user: userData, isAuthenticated: true }),
logout: () => set({ user: null, isAuthenticated: false }),
}));
各ページでのStore使用
これだけでアプリのどこからでも状態の読み書きが可能に:
Aページ(ログイン画面):
import { useAuthStore } from '../store/useAuthStore';
export default function LoginScreen() {
// 不必要な再レンダーを防ぐためlogin関数のみを分割代入
const login = useAuthStore((state) => state.login);
const handleLogin = () => {
// API呼び出し成功を模擬
login({ name: "Ken", avatar: "https://example.com/avatar.jpg" });
};
return <Button title="ログイン" onPress={handleLogin} />;
}
Bページ(ホームヘッダー):
import { useAuthStore } from '../store/useAuthStore';
import { Text, Image } from 'react-native';
export default function Header() {
const user = useAuthStore((state) => state.user);
if (!user) return <Text>ログインしてください</Text>;
return (
<View>
<Image source={{ uri: user.avatar }} style={{ width: 40, height: 40 }} />
<Text>ようこそ, {user.name}さん!</Text>
</View>
);
}
2. APIデータ取得のベストプラクティス:React Query (TanStack Query)
Zustandは「ローカルUI状態」(サイドバーの開閉、ダーク/ライトモードなど)の管理に最適です。 しかし「バックエンドから取得するデータ」(商品リスト、投稿リストなど)にはReact Queryの使用を強く推奨します!
モバイルアプリではネットワーク状態が非常に不安定です(トンネルを通ると接続が切れるなど)。単純なfetchやaxiosだけでは、ローディング状態、エラー処理、再取得、オフラインキャッシュなどを自前で実装する必要があり、非常に煩雑になります。
React Queryがこれを解決します。
インストール:
npm install @tanstack/react-query
無限スクロール商品リストの実装
import { ActivityIndicator, FlatList, Text, View } from 'react-native';
import { useQuery } from '@tanstack/react-query';
// API取得関数定義
const fetchProducts = async () => {
const response = await fetch('https://fakestoreapi.com/products');
if (!response.ok) throw new Error('ネットワークエラー');
return response.json();
};
export default function ProductList() {
// React Queryの魔法のようなHook
const { data, isLoading, isError, error, refetch } = useQuery({
queryKey: ['products'], // キャッシュ管理用のキー
queryFn: fetchProducts,
});
// 1. ローディングUIの自動処理
if (isLoading) return <ActivityIndicator size="large" />;
// 2. エラーUIの自動処理
if (isError) return <Text>エラー発生:{error.message}</Text>;
// 3. 画面レンダリング(プルダウンリフレッシュ機能内蔵)
return (
<FlatList
data={data}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={{ padding: 16, borderBottomWidth: 1 }}>
<Text style={{ fontSize: 18 }}>{item.title}</Text>
<Text style={{ color: 'green' }}>${item.price}</Text>
</View>
)}
// たったこれだけでプルダウン更新完了!
refreshing={isLoading}
onRefresh={refetch}
/>
);
}
[!TIP] FlatListパフォーマンス技 Webでは1000件のデータを表示する場合、
data.map()で全件レンダリングするかもしれません。しかしモバイルでは絶対にNG!メモリ不足でクラッシュします。 React Native組み込みの<FlatList>を使用必須です。これは仮想リスト(Virtualized List)で、「画面内に見えている項目のみ」をレンダリングし、スクロール時に画面上部から消えた要素のメモリを回収して下部に表示予定のデータに再利用します。
ZustandとReact Queryを導入すれば、アプリの基盤は業界トップクラスの水準に達します。 最後の章では、アプリ開発で最も恐れられる大ボスに挑戦します——ストア向けプラットフォーム別ビルドと公開です!