前端状态管理方案对比

2026-06-22 · 6 阅读 · 731字
ReactTypeScriptVue

前端状态管理方案对比

状态管理的本质

前端状态管理要解决的问题是:如何让多个组件共享和同步数据。随着应用规模增长,组件之间的通信变得复杂,状态管理工具应运而生。

状态分类

本地状态 vs 全局状态

// 本地状态:组件内部使用
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

// 全局状态:多个组件共享
// 需要状态管理工具

服务端状态 vs 客户端状态

  • 服务端状态:来自 API 的数据,需要缓存、同步、刷新
  • 客户端状态:UI 状态(主题、侧边栏展开/收起)、表单输入

主流方案对比

方案 适用场景 学习曲线 Bundle 大小 特点
Redux / Redux Toolkit 大型复杂应用 ~12KB 可预测、中间件生态丰富
Zustand 中小型应用 ~1KB 简洁、TypeScript 友好
Pinia (Vue) Vue 3 项目 ~1KB Vue 官方推荐
Jotai React 原子化 ~3KB 原子模型、自然异步
React Context 低频更新 0 内置、无额外依赖
TanStack Query 服务端状态 ~13KB 缓存、自动刷新、SSR
Valtio 响应式代理 ~2KB Proxy 代理、可变更新

Redux Toolkit

核心概念

Redux 基于单一数据源(store)、只读状态(通过 dispatch action 修改)、纯函数 reducer。

import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';

// 创建 slice
const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1; },
    decrement: (state) => { state.value -= 1; },
    setValue: (state, action: PayloadAction<number>) => { state.value = action.payload; },
  },
});

// 创建 store
const store = configureStore({
  reducer: { counter: counterSlice.reducer },
});

// 使用
store.dispatch(counterSlice.actions.increment());
console.log(store.getState().counter.value);

export const { increment, decrement } = counterSlice.actions;
export default store;

React 集成

import { useSelector, useDispatch } from 'react-redux';
import { increment } from './counterSlice';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();
  return <button onClick={() => dispatch(increment())}>{count}</button>;
}

Zustand

Zustand 的 API 极简,不需要 Provider 包裹。

import { create } from 'zustand';

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

const useStore = create<BearStore>((set) => ({
  bears: 0,
  increase: () => set((state) => ({ bears: state.bears + 1 })),
  reset: () => set({ bears: 0 }),
}));

// 在组件中使用
function BearCounter() {
  const bears = useStore((state) => state.bears);
  const increase = useStore((state) => state.increase);
  return (
    <div>
      <p>{bears} 只熊</p>
      <button onClick={increase}>增加</button>
    </div>
  );
}

Zustand 中间件

import { persist } from 'zustand/middleware';

const useStore = create(
  persist(
    (set) => ({
      theme: 'light',
      setTheme: (theme: string) => set({ theme }),
    }),
    { name: 'theme-storage' }
  )
);

Jotai(原子化)

Jotai 采用原子模型,每个 atom 是独立的状态单元,通过组合构建复杂状态。

import { atom, useAtom } from 'jotai';

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

// 派生 atom(类似 computed)
const doubleAtom = atom((get) => get(countAtom) * 2);

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

function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const [double] = useAtom(doubleAtom);
  return (
    <div>
      <p>{count} × 2 = {double}</p>
      <button onClick={() => setCount(c => c + 1)}>增加</button>
    </div>
  );
}

Pinia(Vue 3)

import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    token: null as string | null,
  }),
  getters: {
    isLoggedIn: (state) => !!state.token,
    greeting: (state) => `你好, ${state.name}!`,
  },
  actions: {
    async login(username: string, password: string) {
      const { token, name } = await api.login(username, password);
      this.token = token;
      this.name = name;
    },
    logout() {
      this.token = null;
      this.name = '';
    },
  },
});

服务端状态管理:TanStack Query

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

function TodoList() {
  const queryClient = useQueryClient();

  const { data, isLoading } = useQuery({
    queryKey: ['todos'],
    queryFn: async () => {
      const res = await fetch('/api/todos');
      return res.json();
    },
  });

  const addMutation = useMutation({
    mutationFn: async (newTodo) => {
      return fetch('/api/todos', { method: 'POST', body: JSON.stringify(newTodo) });
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['todos'] }); // 自动刷新
    },
  });

  if (isLoading) return <div>加载中...</div>;
  return (
    <div>
      {data?.map(todo => <div key={todo.id}>{todo.text}</div>)}
      <button onClick={() => addMutation.mutate({ text: '新任务' })}>添加</button>
    </div>
  );
}

选型建议

// 选型决策树:

// 1. 主要是服务端数据缓存?
// → TanStack Query / SWR / RTK Query

// 2. 小型应用,少量全局状态?
// → React Context / Vue provide-inject

// 3. 中小型应用,需要简洁方案?
// → Zustand(React) / Pinia(Vue)

// 4. 大型应用,需要严格的数据流?
// → Redux Toolkit

// 5. 喜欢原子化、组合式思维?
// → Jotai / Recoil

// 6. 需要状态持久化?
// → Zustand + persist 中间件 / Redux Toolkit + redux-persist

总结

没有最好的状态管理方案,只有最合适的。推荐策略:服务端状态用 TanStack Query,客户端全局状态用 Zustand 或 Redux Toolkit,简单场景用 React Context。避免过度设计,只在真正需要时引入全局状态管理。