狀態管理與後端 API 資料串接
當你的 App 只有兩三個頁面時,你可以用 React 內建的 useState 和 props 傳遞資料。但當 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 中,網路狀態是非常不穩定的(使用者可能過個隧道就斷網了)。如果我們只用單純的 fetch 或 axios,我們必須自己處理 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 最令人聞風喪膽的大魔王——打包與上架雙平台商店!