状态管理的演变
React 生态的状态管理方案经历了激烈的演变:早期 Redux 一统天下,但样板代码繁多;MobX 以响应式编程简化了写法;Recoil 尝试原子化状态;Zustand 以极简 API 赢得青睐;而 React Query 的出现让人们意识到:大多数"全局状态"其实根本不是客户端状态,而是服务端状态。
在 React Native 应用中,状态可以分为两大类,它们有根本性的不同,需要用不同工具管理:
客户端状态
- 当前用户信息(已登录)
- UI 状态(Modal 是否打开)
- 主题设置(深色/浅色)
- 购物车商品列表
- 表单输入值
服务端状态
- 帖子列表(来自 API)
- 用户详情(来自 API)
- 评论数据(来自 API)
- 搜索结果
- 通知列表
核心名词解释
Local State(局部状态)
组件内部的状态,用 useState 或 useReducer 管理,只有该组件和其子组件能访问。大多数 UI 状态都应该是局部状态。
Global State(全局状态)
需要在多个不相关组件间共享的客户端状态。如当前登录用户、应用主题。用 Context 或 Zustand 管理。
Server State(服务端状态)
存储在服务器上、通过 API 获取的异步数据。有缓存、过期、重新获取等概念。用 React Query 或 SWR 管理,而不是 Redux/Zustand。
useReducer
useState 的升级版,适合有多个子值或下一个状态依赖上一个状态的复杂逻辑。通过 dispatch action 触发状态变更,便于测试和调试。
Context
React 内置的跨组件状态共享机制。缺点:Context 值改变时,所有消费该 Context 的组件都会重渲染,需要配合 memo 或拆分 Context 避免性能问题。
Zustand
轻量级全局状态库(~1KB),无需 Provider 包裹,用选择器(selector)精确订阅所需状态,避免不必要的重渲染。API 极简,适合替代 Redux。
React Query(TanStack Query)
服务端状态管理库,提供缓存、后台同步、错误重试、分页等开箱即用功能。核心 Hook:useQuery(读取)、useMutation(写入)。
Stale-While-Revalidate
React Query 的缓存策略:先返回缓存数据(stale,即便过期也立即展示),同时在后台重新获取(revalidate)最新数据,更新后再渲染。极大改善用户体验。
状态流向图
React Native 状态管理全景
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌───────────────────────────────────────────┐
│ UI 组件层 │
│ │
│ [本地状态] useState / useReducer │
│ ↕ 组件内部,不需要外部库 │
│ │
│ [全局 UI 状态] Zustand Store │
│ ↓ dispatch action │
│ ↑ useStore(selector) 精确订阅 │
│ │
│ [服务端状态] React Query │
│ ↓ useQuery / useMutation │
│ ↑ 缓存数据,自动后台刷新 │
└───────────────────────────────────────────┘
↕ ↕
┌──────────────┐ ┌──────────────────────┐
│ Zustand Store │ │ TanStack Query │
│ (客户端全局) │ │ Cache(服务端状态) │
│ │ │ │
│ authUser │ │ posts: [...], │
│ theme │ │ user/123: {...}, │
│ cartItems │ │ search?q=react: [] │
└──────────────┘ └──────────────────────┘
↕
┌──────────────────┐
│ Axios / fetch │
│ API Server │
└──────────────────┘
Zustand:轻量全局状态
Zustand 是目前 React Native 社区最受欢迎的全局状态库之一。它的核心优势是:无需 Provider 包裹,任何组件都可以直接访问 Store;选择器机制确保只有订阅的状态变化时才重渲染。
// store/authStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface User {
id: string;
name: string;
avatar: string;
}
interface AuthState {
user: User | null;
token: string | null;
isLoggedIn: boolean;
login: (user: User, token: string) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
// persist 中间件:自动持久化到 AsyncStorage
persist(
(set) => ({
user: null,
token: null,
isLoggedIn: false,
login: (user, token) => set({ user, token, isLoggedIn: true }),
logout: () => set({ user: null, token: null, isLoggedIn: false }),
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// 使用:只订阅 user,user 变化才重渲染,token 变化不触发
function ProfileHeader() {
const user = useAuthStore(state => state.user); // 精确订阅
const logout = useAuthStore(state => state.logout);
if (!user) return null;
return (
<View>
<Text>{user.name}</Text>
<Pressable onPress={logout}><Text>退出</Text></Pressable>
</View>
);
}
React Query:服务端状态管理
// App.tsx — 配置 QueryClient
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5分钟内不重新请求
retry: 2, // 失败重试2次
refetchOnWindowFocus: false, // App 切回前台时不自动刷新
},
},
});
export default function App() {
return (
<QueryClientProvider client={queryClient}>
{/* 导航和其他内容 */}
</QueryClientProvider>
);
}
// hooks/usePosts.ts — 封装数据请求
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '../lib/api';
export function usePosts() {
return useQuery({
queryKey: ['posts'], // 缓存键,唯一标识这份数据
queryFn: () => api.getPosts(),
});
}
export function useLikePost() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (postId: string) => api.likePost(postId),
// 乐观更新:先修改本地缓存,成功后服务器已同步,失败则回滚
onMutate: async (postId) => {
await queryClient.cancelQueries({ queryKey: ['posts'] });
const prev = queryClient.getQueryData(['posts']);
queryClient.setQueryData(['posts'], (old: Post[]) =>
old.map(p => p.id === postId ? { ...p, likes: p.likes + 1 } : p)
);
return { prev }; // 快照,用于回滚
},
onError: (_, __, ctx) => {
if (ctx?.prev) queryClient.setQueryData(['posts'], ctx.prev);
},
});
}
90% 定律
在真实项目中,90% 以上的"全局状态需求"其实是服务端状态(帖子列表、用户信息、通知等)。把这些用 React Query 管理后,Zustand 只需要处理少量真正的客户端全局状态(当前登录用户、主题、语言设置),大量简化状态管理复杂度。
useReducer 的适用场景
当组件有 3 个以上相关状态,或状态更新逻辑复杂(如表单验证),才考虑用 useReducer 替代 useState。不要为了"看起来更专业"就到处用 useReducer,useState 更简单更直观。