前端状态管理全景对比与选型指南

系统性对比前端状态管理方案:Flux/Redux(RTK)、Zustand、Jotai、Recoil、Valtio、Pinia、Vuex、TanStack Query / SWR(服务端状态)、Context API、URL State、Local Storage State。按状态类型分类(服务端/客户端全局/本地/UI),给出选型决策树、性能对比、跨框架方案与实战代码。覆盖 Vue3 + React 生态。

状态管理的核心问题不是「用什么库」,而是「状态放在哪里」。 服务端状态、客户端全局状态、组件本地状态、URL 状态各有最佳归宿,选对位置比选对工具更重要。


一、状态分类

1.1 四种状态类型

状态 = 数据 + 变化规则

┌─────────────────────────────────────────┐
│  A. 服务端状态(Server State)           │
│     特征:远程、异步、不可预测            │
│     例子:用户信息、订单列表、商品详情    │
│     方案:TanStack Query / SWR / Apollo   │
├─────────────────────────────────────────┤
│  B. 客户端全局状态(Client Global)      │
│     特征:本地、同步、跨组件共享          │
│     例子:主题、登录态、侧边栏展开        │
│     方案:Redux / Zustand / Pinia / Jotai │
├─────────────────────────────────────────┤
│  C. 组件本地状态(Component Local)      │
│     特征:局部、短暂、不共享              │
│     例子:表单输入、开关状态、动画标志    │
│     方案:useState / ref / reactive       │
├─────────────────────────────────────────┤
│  D. URL 状态(URL State)                │
│     特征:可分享、可刷新保持、可后退      │
│     例子:分页页码、筛选条件、搜索关键词  │
│     方案:URL query params / Router state │
└─────────────────────────────────────────┘

二、服务端状态管理

2.1 TanStack Query(React/Vue/Svelte)

// React 示例
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// 读取
function useUser(userId: string) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
    staleTime: 5 * 60 * 1000,      // 5 分钟内不重新请求
    gcTime: 10 * 60 * 1000,        // 缓存保留 10 分钟
    retry: 3,                      // 失败重试 3 次
    refetchOnWindowFocus: false,   // 切换回页面不自动刷新
  });
}

// 写入 + 乐观更新
function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: UserUpdate) =>
      fetch(`/api/users/${data.id}`, { method: 'PATCH', body: JSON.stringify(data) }),
    onMutate: async (newData) => {
      // 乐观更新:先改 UI,后发请求
      await queryClient.cancelQueries({ queryKey: ['user', newData.id] });
      const prev = queryClient.getQueryData(['user', newData.id]);
      queryClient.setQueryData(['user', newData.id], (old) => ({ ...old, ...newData }));
      return { prev };
    },
    onError: (err, newData, context) => {
      // 出错回滚
      queryClient.setQueryData(['user', newData.id], context?.prev);
    },
    onSettled: (data, err, newData) => {
      // 最终同步
      queryClient.invalidateQueries({ queryKey: ['user', newData.id] });
    }
  });
}

核心能力

  • 自动缓存、去重、后台刷新
  • 乐观更新、重试、分页、无限滚动
  • DevTools 调试
  • SSR 支持

2.2 SWR(React)

import useSWR from 'swr';
import useSWRMutation from 'swr/mutation';

const fetcher = (url: string) => fetch(url).then(r => r.json());

function Profile() {
  const { data, error, isLoading, mutate } = useSWR('/api/user', fetcher, {
    refreshInterval: 30000,      // 每 30s 自动刷新
    revalidateOnFocus: true,     // 聚焦时刷新
    dedupingInterval: 2000,      // 2s 内去重
  });

  if (isLoading) return <div>loading...</div>;
  if (error) return <div>failed to load</div>;
  return <div>hello {data.name}!</div>;
}

2.3 服务端状态选型

维度TanStack QuerySWR
框架支持React/Vue/Svelte/ SolidReact
乐观更新内置完善需手动
分页/无限滚动内置 hooks需组合
开发体验稍复杂但更强极简
推荐✅ 大型项目✅ 小型/快速原型

三、React 客户端全局状态

3.1 Redux Toolkit(RTK)

适合:大型应用、严格的数据流、团队需要统一模式

// store.ts
import { configureStore, createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 异步 thunk
const fetchUser = createAsyncThunk('user/fetch', async (userId: string) => {
  const res = await fetch(`/api/users/${userId}`);
  return res.json();
});

// Slice
const userSlice = createSlice({
  name: 'user',
  initialState: { data: null, loading: false, error: null },
  reducers: {
    logout: (state) => { state.data = null; }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => { state.loading = true; })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  }
});

export const store = configureStore({
  reducer: { user: userSlice.reducer }
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// hooks.ts — 类型安全封装
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from './store';

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

3.2 Zustand

适合:中小型应用、快速开发、不喜欢 Redux 样板代码

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface BearState {
  bears: number;
  increase: () => void;
  decrease: () => void;
  reset: () => void;
}

const useBearStore = create<BearState>()(
  persist(
    (set) => ({
      bears: 0,
      increase: () => set((state) => ({ bears: state.bears + 1 })),
      decrease: () => set((state) => ({ bears: state.bears - 1 })),
      reset: () => set({ bears: 0 }),
    }),
    { name: 'bear-storage' } // localStorage 持久化
  )
);

// 使用(无 Provider!)
function BearCounter() {
  const bears = useBearStore((state) => state.bears);
  return <h1>{bears} bears</h1>;
}

Zustand vs Redux

维度ZustandRedux
学习成本极低中等
样板代码几乎没有较多
DevTools支持原生强大
中间件简洁丰富(saga/thunk)
时间旅行需配置原生
适用规模小到中中到大

3.3 Jotai(原子化状态)

适合:细粒度状态、派生状态复杂、喜欢函数式风格

import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';

// 基础原子
const countAtom = atom(0);

// 派生原子(只读)
const doubleCountAtom = atom((get) => get(countAtom) * 2);

// 可写派生原子
const incrementAtom = atom(null, (get, set, amount: number) => {
  set(countAtom, (c) => c + amount);
});

// 异步原子
const userAtom = atom(async () => {
  const res = await fetch('/api/user');
  return res.json();
});

// 使用
function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const double = useAtomValue(doubleCountAtom);
  const increment = useSetAtom(incrementAtom);

  return (
    <div>
      <p>{count} / {double}</p>
      <button onClick={() => setCount((c) => c + 1)}>+1</button>
      <button onClick={() => increment(5)}>+5</button>
    </div>
  );
}

3.4 Valtio(Mutable State / Proxy)

适合:喜欢直接修改对象、从 Vue/MobX 迁移

import { proxy, useSnapshot } from 'valtio';

const state = proxy({
  user: { name: 'Alice', age: 30 },
  todos: [] as { id: number; text: string; done: boolean }[],
});

function Profile() {
  const snap = useSnapshot(state);

  // 直接修改(自动触发重渲染)
  const increaseAge = () => { state.user.age++; };

  return (
    <div>
      <p>{snap.user.name} is {snap.user.age}</p>
      <button onClick={increaseAge}>过生日</button>
    </div>
  );
}

四、Vue 客户端全局状态

4.1 Pinia(Vue 官方推荐)

// stores/user.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useUserStore = defineStore('user', () => {
  // State
  const user = ref<User | null>(null);
  const loading = ref(false);

  // Getters(computed)
  const isLoggedIn = computed(() => user.value !== null);
  const displayName = computed(() => user.value?.name ?? 'Guest');

  // Actions
  async function login(credentials: Credentials) {
    loading.value = true;
    try {
      user.value = await api.login(credentials);
    } finally {
      loading.value = false;
    }
  }

  function logout() {
    user.value = null;
  }

  return { user, loading, isLoggedIn, displayName, login, logout };
}, {
  // 持久化(需 pinia-plugin-persistedstate)
  persist: { paths: ['user'] }
});
<!-- Component.vue -->
<script setup>
import { useUserStore } from '@/stores/user';
import { storeToRefs } from 'pinia';

const userStore = useUserStore();
const { isLoggedIn, displayName } = storeToRefs(userStore); // 保持响应式解构
const { login, logout } = userStore;                         // 方法直接解构
</script>

4.2 Pinia vs Vuex

维度PiniaVuex 4
API 风格Composition API(setup)Options API(mutations/actions)
TypeScript原生友好需类型封装
模块自动(每个 store 独立)需手动注册
体积更小(~1KB)稍大
DevTools支持支持
推荐✅ Vue 3 首选⚠️ 维护模式

五、跨框架方案

5.1 信号(Signals)— 未来趋势

// Solid / Preact / Vue Vapor / Angular 都在拥抱 Signals
// 核心思想:细粒度响应,只有读取 Signal 的组件才更新

// Vue(Reactivity Vapor 模式预览)
import { ref, computed, effect } from '@vue/reactivity';

const count = ref(0);
const double = computed(() => count.value * 2);

effect(() => {
  console.log(double.value); // 依赖追踪,自动重新执行
});

count.value++; // 触发 effect

5.2 XState(状态机)

import { createMachine, interpret } from 'xstate';

const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: { on: { TOGGLE: 'active' } },
    active: { on: { TOGGLE: 'inactive' } }
  }
});

// 适合:复杂状态流转(订单状态、多步骤表单、关卡游戏)
// 不适合:简单计数器(过度设计)

六、选型决策树

状态类型是什么?
├── 服务端数据(API 返回)
│   └── 用 TanStack Query(React/Vue)或 SWR(React)
├── 客户端全局状态
│   ├── React 生态
│   │   ├── 大型团队 + 严格数据流        → Redux Toolkit
│   │   ├── 中小型 + 极简 API            → Zustand ✅ 多数场景
│   │   ├── 细粒度 + 派生状态复杂        → Jotai
│   │   ├── 喜欢直接修改对象             → Valtio
│   │   └── 复杂状态机                   → XState
│   │
│   └── Vue 生态
│       └── Pinia(唯一推荐)            → Pinia ✅
├── 组件本地状态
│   ├── React → useState / useReducer / useRef
│   ├── Vue   → ref / reactive / computed
│   └── 跨组件但范围有限 → Context(React)/ Provide(Vue)
└── URL 状态
    └── 分页/筛选/搜索   → URL query params(可分享、可刷新)
        临时弹窗状态      → 组件本地(不要污染 URL)

七、组合使用模式

// 推荐的多层状态管理架构

// 1. 服务端状态:TanStack Query(缓存、刷新、乐观更新)
const { data: user } = useQuery({ queryKey: ['user'], queryFn: fetchUser });

// 2. 客户端全局状态:Zustand(主题、UI 状态、本地化)
const theme = useThemeStore((s) => s.theme);
const sidebarOpen = useUIStore((s) => s.sidebarOpen);

// 3. 组件本地状态:useState(表单、开关、临时值)
const [formData, setFormData] = useState({ name: '', email: '' });

// 4. URL 状态:分页、筛选
const [searchParams, setSearchParams] = useSearchParams();
const page = Number(searchParams.get('page')) || 1;

参考与延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「frontend」更多文章

  1. API 设计与 BFF 层:REST、GraphQL、tRPC 选型与前后端协作
  2. WebAssembly 前端工程化实践:编译链、性能对比与混合架构
  3. 现代浏览器 API 与 Web 平台能力地图