状态管理方案

What — 是什么

状态管理解决跨组件共享数据的同步问题,从 Redux 的单一 store 到 Zustand/Pinia 的轻量方案,现代前端状态管理越来越简洁。

核心概念:

  • Store:集中存储共享状态
  • Dispatch/Action:描述状态变更的意图
  • Selector:从 store 中精确读取需要的状态
  • Middleware:处理异步逻辑(Redux Thunk/Saga)

主流方案:

方案生态体积学习成本适用场景
Redux ToolkitReact大型项目、强约束
ZustandReact极小中小型项目
Jotai/RecoilReact原子化细粒度
PiniaVueVue 3 官方推荐
VuerxVueVue 2 遗留项目

关键特性:

  • Redux:单一数据源、纯函数 reducer、可预测
  • Zustand:无 boilerplate、hook 原生、性能好
  • Pinia:Vue DevTools 集成、TypeScript 友好、模块化

Why — 为什么

适用场景:

  • 多组件共享状态(用户信息、购物车、主题)
  • 跨页面状态持久化
  • 复杂异步数据流

何时不需要:

  • 仅父子组件通信 → props/emit
  • 仅跨层级传递 → Context/provide-inject
  • 局部 UI 状态 → 组件内 useState/ref

优缺点:

  • ✅ 优点:
    • 集中管理,状态可追踪
    • 组件解耦,不依赖层级关系
    • DevTools 支持时间旅行调试
  • ❌ 缺点:
    • 增加复杂度,小项目过度设计
    • 全局 store 易变成”大杂烩”
    • 状态与组件耦合度降低,心智负担增加

How — 怎么用

Zustand(React 推荐)

import { create } from 'zustand';

const useStore = create((set) => ({
    user: null,
    cart: [],
    login: (user) => set({ user }),
    logout: () => set({ user: null }),
    addToCart: (item) =>
        set((state) => ({ cart: [...state.cart, item] })),
    cartTotal: () =>
        useStore.getState().cart.reduce((sum, i) => sum + i.price, 0),
}));

// 组件中使用
function Cart() {
    const cart = useStore((s) => s.cart); // 精确选择,避免不必要渲染
    const addToCart = useStore((s) => s.addToCart);

    return (
        <div>
            {cart.map(item => <div key={item.id}>{item.name}</div>)}
            <button onClick={() => addToCart({ id: 1, name: 'Book', price: 29 })}>
                加入购物车
            </button>
        </div>
    );
}

Pinia(Vue 推荐)

// stores/user.js
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
    state: () => ({
        user: null,
        token: localStorage.getItem('token'),
    }),
    getters: {
        isLoggedIn: (state) => !!state.token,
    },
    actions: {
        async login(credentials) {
            const res = await api.login(credentials);
            this.user = res.user;
            this.token = res.token;
            localStorage.setItem('token', res.token);
        },
        logout() {
            this.user = null;
            this.token = null;
            localStorage.removeItem('token');
        },
    },
});

// 或 Composition API 风格
export const useUserStore = defineStore('user', () => {
    const user = ref(null);
    const token = ref(localStorage.getItem('token'));
    const isLoggedIn = computed(() => !!token.value);

    async function login(credentials) { /* ... */ }
    function logout() { /* ... */ }

    return { user, token, isLoggedIn, login, logout };
});
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
</script>

<template>
    <div v-if="userStore.isLoggedIn">
        {{ userStore.user.name }}
        <button @click="userStore.logout()">退出</button>
    </div>
</template>

Redux Toolkit(大型 React 项目)

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

const cartSlice = createSlice({
    name: 'cart',
    initialState: { items: [] },
    reducers: {
        addItem: (state, action) => {
            state.items.push(action.payload); // Immer 允许直接修改
        },
        removeItem: (state, action) => {
            state.items = state.items.filter(i => i.id !== action.payload);
        },
    },
});

const store = configureStore({
    reducer: { cart: cartSlice.reducer },
});

// 组件中使用
import { useSelector, useDispatch } from 'react-redux';
const items = useSelector((s) => s.cart.items);
const dispatch = useDispatch();
dispatch(cartSlice.actions.addItem({ id: 1, name: 'Book' }));

常见问题与踩坑

问题原因解决方案
Zustand selector 导致不必要渲染返回新对象引用shallow 比较或返回原始值
Pinia action 中 this 指向action 是普通函数,this 正确绑定不要用箭头函数定义 action
Redux boilerplate 多传统 Redux 写法繁琐用 Redux Toolkit 的 createSlice
全局 store 臃肿所有状态放一个 store按领域拆分多个 store/slice

最佳实践

  • React 中小项目用 Zustand,大型用 Redux Toolkit
  • Vue 3 一律用 Pinia
  • 按领域拆分 store,不要一个全局大 store
  • 组件局部状态不放全局 store

面试题

Q1: 什么时候需要引入状态管理?

当多个不相关组件需要共享同一份数据(如用户信息、主题)、跨页面状态需要持久化、组件层级过深导致 props 透传(prop drilling)严重时,应引入状态管理。仅父子通信或局部 UI 状态不需要。

Q2: Redux 的核心工作流程是什么?

单向数据流:组件 dispatch(action) → reducer 纯函数处理 action 返回新 state → store 更新 → 组件通过 selector 订阅新 state 重新渲染。异步操作通过 middleware(Thunk/Saga)处理。

Q3: Zustand 相比 Redux 有什么优势?

① 无 boilerplate,无需 action/reducer/slice 模板代码;② 基于 Hook,组件内直接 useStore(s => s.xxx);③ 内置 selector 优化,默认浅比较避免不必要渲染;③ 体积极小(~1KB),无需 Provider 包裹。

Q4: 服务端状态和客户端状态有什么区别?

服务端状态来自远程 API,是异步的、可能过期的、可能有多个消费者共享的副本,适合用 React Query/SWR 管理(自动缓存、失效、重请求);客户端状态存在于浏览器内存,是同步的、完全可控的,适合 Zustand/Pinia 管理。两者不应混在同一个 store。

Q5: 为什么说”全局 store 易变成大杂烩”?如何避免?

所有状态都塞进一个 store 导致职责不清、难维护、按需加载困难。避免方式:按领域(user/cart/theme)拆分多个独立 store/slice;区分服务端状态(React Query)和客户端状态(Zustand);局部 UI 状态留在组件内。


相关链接: