狀態管理與後端 API 資料串接

當你的 App 只有兩三個頁面時,你可以用 React 內建的 useStateprops 傳遞資料。但當 App 越來越龐大:

  • 「首頁」需要顯示使用者的頭像。
  • 「個人檔案頁」可以修改頭像。
  • 「側邊欄」也需要顯示頭像。

如果你還是用 props 一層一層傳遞,會引發著名的「Props Drilling(屬性地獄)」。這時候我們就需要 全域狀態管理 (Global State Management)

1. 拋棄 Redux,擁抱 Zustand!

過去,React 開發者最愛用的是 Redux。但 Redux 的設定極度繁瑣,你需要寫一堆 Action, Reducer, Dispatch,這對開發微型專案來說太痛苦了。

這幾年,Zustand (德文「狀態」的意思) 成為了現代 React/RN 開發者的最愛。它極度輕量、不需要用 <Provider> 包覆整個 App、寫法就跟普通的 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

就這麼簡單!現在你可以在 App 的任何角落讀取或修改這個狀態:

在 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

在手機 App 中,網路狀態是非常不穩定的(使用者可能過個隧道就斷網了)。如果我們只用單純的 fetchaxios,我們必須自己處理 loading 狀態、error 狀態、重新抓取、以及離線快取... 寫起來會崩潰。

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. 畫面渲染,並內建「下拉更新 (Pull-to-refresh)」功能
  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 效能神技 在網頁上,如果你要顯示 1000 筆資料,你可能會用 data.map() 把他們全部渲染出來。但在手機上千萬別這麼做!這會導致記憶體耗盡而閃退。 必須使用 React Native 內建的 <FlatList>。它是一個虛擬列表 (Virtualized List),它只會渲染「目前在螢幕範圍內」的項目,當你往下捲動時,它會回收上面滑過去的元件記憶體給下面即將出現的資料使用。

有了 Zustand 和 React Query,你的 App 內在骨幹已經達到了業界旗艦級標準。 最後一個章節,我們要來挑戰開發 App 最令人聞風喪膽的大魔王——打包與上架雙平台商店

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!