还在为 Redux 头疼?Zustand 让 React 状态管理轻到能 “揣兜里”

在 React 开发中,状态管理一直是个绕不开的话题。你是不是也经历过:用 useState 管理全局状态时,组件嵌套太深导致 props 层层传递("props 钻取");用 Redux 又觉得样板代码太多,配置繁琐;用 useContext + useReducer 吧,复杂场景下性能又跟不上?

今天给大家推荐一个 "宝藏级" 状态管理库 ------Zustand。它轻量、简洁,完全基于 hooks,几行代码就能实现全局状态管理,比 Redux 简单 10 倍,还没有 Context 的性能问题。

为什么需要 Zustand?现有方案的痛点

先说说我们熟悉的状态管理方案都有哪些问题:

  • useState + props:适合组件内部状态,但全局状态需要层层传递,嵌套越深越麻烦("props 地狱");
  • Redux :功能强大但配置复杂,需要 storereduceractionmiddleware 等,样板代码多,新手劝退;
  • useContext + useReducer:虽然比 Redux 简单,但 Context 会导致不必要的重渲染(只要 Context 变化,所有消费组件都会更新);
  • MobX:需要理解 "响应式" 概念,学习成本高,且写法不够 "React 原生"。

Zustand 完美解决了这些问题:

  • 极简 API:用 hooks 定义和使用状态,几行代码搞定全局状态;
  • 无 Provider 包裹 :不需要像 Context 那样用 Provider 包裹整个应用;
  • 精准更新:组件只订阅自己需要的状态,避免无关重渲染;
  • 支持中间件:可以轻松集成持久化、日志等功能;
  • 体积超小:核心代码只有 1KB 左右,几乎不增加项目体积。

Zustand 核心用法:3 步搞定全局状态

Zustand 的设计非常简洁,核心只有一个create函数,用来创建 "状态仓库(store)",然后在组件中用自定义 hook 访问。

(1)安装依赖

bash 复制代码
pnpm i zustand

(2)创建第一个 Store:计数器案例

先从最简单的计数器开始,感受 Zustand 的用法:

javascript 复制代码
// store/count.js
import { create } from 'zustand';

// 用create创建store,返回一个自定义hook(通常命名为useXxxStore)
export const useCounterStore = create((set) => ({
  // 1. 定义状态
  count: 0,

  // 2. 定义修改状态的方法(类似reducer,但更灵活)
  increment: () => set((state) => ({ count: state.count + 1 })), // 累加
  decrement: () => set((state) => ({ count: state.count - 1 })), // 递减
}));

核心说明

  • create函数接收一个回调,回调参数set是一个函数,用于更新状态(类似setState);
  • set可以接收一个函数((state) => newState),通过旧状态计算新状态(避免状态依赖问题);
  • 返回的useCounterStore是一个自定义 hook,组件通过它访问状态和方法。

(3)在组件中使用 Store

不需要 Provider 包裹,直接在组件中调用useCounterStore

jsx 复制代码
// components/Counter.jsx
import { useCounterStore } from '../store/count';

const Counter = () => {
  // 从store中解构出状态和方法
  const { count, increment, decrement } = useCounterStore();

  return (
    <div>
      <p>计数器:{count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
};

甚至可以在多个组件中使用,状态会自动同步:

jsx 复制代码
// App.jsx
import { useCounterStore } from './store/count';
import Counter from './components/Counter';

function App() {
  // 在App组件中也能访问同一个count
  const { count } = useCounterStore();

  return (
    <div>
      <h1>App中的计数:{count}</h1>
      <Counter /> {/* 这里的count和App中的count完全同步 */}
    </div>
  );
}

神奇之处:没有 Provider,没有 Context,直接调用 hook 就能共享状态,这就是 Zustand 的简洁所在。

进阶实战:用 Zustand 管理复杂状态

Zustand 不仅能处理简单计数器,复杂的列表、异步请求也能轻松应对,而且支持 "模块化拆分",让代码更清晰。

(1)管理 Todo 列表:增删改查全实现

javascript 复制代码
// store/todos.js
import { create } from 'zustand';

export const useTodosStore = create((set) => ({
  // 初始状态:一个Todo数组
  todos: [
    { id: 1, text: '学习Zustand', completed: false },
    { id: 2, text: '写一篇博客', completed: false },
  ],

  // 添加Todo
  addTodo: (text) => set((state) => ({
    todos: [
      ...state.todos,
      { id: state.todos.length + 1, text, completed: false },
    ],
  })),

  // 切换Todo完成状态
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ),
  })),

  // 删除Todo
  removeTodo: (id) => set((state) => ({
    todos: state.todos.filter(todo => todo.id !== id),
  })),
}));

在组件中使用:

jsx 复制代码
// components/TodoList.jsx
import { useTodosStore } from '../store/todos';
import { useState } from 'react';

const TodoList = () => {
  const [text, setText] = useState('');
  const { todos, addTodo, toggleTodo, removeTodo } = useTodosStore();

  return (
    <div>
      <input 
        type="text" 
        value={text} 
        onChange={(e) => setText(e.target.value)} 
      />
      <button onClick={() => { addTodo(text); setText(''); }}>
        添加
      </button>

      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <input 
              type="checkbox" 
              checked={todo.completed} 
              onChange={() => toggleTodo(todo.id)} 
            />
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
            <button onClick={() => removeTodo(todo.id)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

亮点:所有 Todo 操作逻辑集中在 store 中,组件只负责 UI 渲染,符合 "状态与 UI 分离" 的原则。

(2)处理异步请求:API 数据状态管理

实际项目中,经常需要管理 API 请求的 "加载中、数据、错误" 状态。Zustand 可以轻松封装这些逻辑:

步骤 1:封装 API 请求函数

javascript 复制代码
// api/repo.js
import axios from './config'; // 配置了baseURL的axios实例

// 获取用户的仓库列表
export const getRepoList = async (username) => {
  const res = await axios.get(`/users/${username}/repos`);
  return res.data;
};

步骤 2:创建管理请求状态的 Store

javascript 复制代码
// store/repos.js
import { create } from 'zustand';
import { getRepoList } from '../api/repo';

export const useRepoStore = create((set) => ({
  repos: [], // 仓库列表数据
  loading: false, // 加载状态
  error: null, // 错误信息

  // 异步获取仓库列表
  fetchRepos: async (username = 'yourusername') => {
    // 开始请求:设置loading为true
    set({ loading: true, error: null });
    try {
      // 调用API
      const data = await getRepoList(username);
      // 请求成功:更新数据,关闭loading
      set({ repos: data, loading: false });
    } catch (err) {
      // 请求失败:记录错误,关闭loading
      set({ error: err.message, loading: false });
    }
  },
}));

步骤 3:在组件中使用异步状态

jsx 复制代码
// components/RepoList.jsx
import { useEffect } from 'react';
import { useRepoStore } from '../store/repos';

const RepoList = () => {
  const { repos, loading, error, fetchRepos } = useRepoStore();

  // 组件挂载时请求数据
  useEffect(() => {
    fetchRepos();
  }, [fetchRepos]);

  // 处理加载和错误状态
  if (loading) return <p>加载中...</p>;
  if (error) return <p>出错了:{error}</p>;

  return (
    <div>
      <h2>仓库列表</h2>
      <ul>
        {repos.map(repo => (
          <li key={repo.id}>
            <a href={repo.html_url} target="_blank" rel="noreferrer">
              {repo.name}
            </a>
            <p>{repo.description || '无描述'}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};

优势:把 "加载中、数据、错误" 的状态管理逻辑封装在 store 中,组件不用关心请求细节,只需渲染 UI。

(3)模块化:按功能拆分 Store

当项目变大时,建议按功能拆分 store(如count.jstodos.jsrepos.js),避免一个 store 过于庞大。

plaintext 复制代码
src/
├── store/
│   ├── count.js       // 计数器相关状态
│   ├── todos.js       // Todo列表相关状态
│   └── repos.js       // 仓库请求相关状态
└── components/        // 组件中按需引入对应的store

这种方式比 Redux 的 "单一 store + 多个 reducer" 更灵活,维护成本更低。

四、Zustand 为什么比其他方案更优?

对比主流状态管理方案,Zustand 的优势非常明显:

特性 Zustand Redux useContext+useReducer
代码量 极少(无样板代码) 多(action、reducer 等) 较多(需创建 Context)
学习成本 低(hooks 语法) 高(概念多) 中(需理解 Context)
组件中使用方式 直接调用 hook 需用 useSelector/useDispatch 需用 useContext
Provider 包裹 不需要 需要(Provider) 需要(Context.Provider)
适合场景 中小型项目、快速开发 大型项目、复杂状态逻辑 小型项目、简单跨组件共享

进阶技巧:这些功能让 Zustand 更强大

(1)状态持久化(刷新不丢失)

zustand-persist插件可以将状态保存到localStoragesessionStorage,刷新页面后状态不丢失:

bash 复制代码
pnpm i zustand-persist
javascript 复制代码
// store/todos.js(持久化改造)
import { create } from 'zustand';
import { persist } from 'zustand/middleware'; // 引入持久化中间件

export const useTodosStore = create(
  persist(
    (set) => ({
      todos: [],
      addTodo: (text) => set((state) => ({
        todos: [...state.todos, { id: Date.now(), text, completed: false }],
      })),
      // ...其他方法
    }),
    {
      name: 'todos-storage', // localStorage的key
      getStorage: () => localStorage, // 用localStorage存储
    }
  )
);

(2)选择状态(避免不必要的重渲染)

默认情况下,组件会在 store 中的任何状态变化时重新渲染。可以用 "选择器" 只订阅需要的状态,优化性能:

jsx 复制代码
// 只订阅count,其他状态变化时不重渲染
const count = useCounterStore(state => state.count);

// 同时订阅多个状态(用对象或数组)
const { count, increment } = useCounterStore(state => ({
  count: state.count,
  increment: state.increment,
}));

总结:Zustand 让状态管理回归简单

Zustand 的设计理念是 "以最小的成本解决状态共享问题",它的核心优势是:

  1. 简洁 :用create函数创建 store,用 hook 访问,无需 Provider;
  2. 灵活:支持同步状态、异步请求、模块化拆分;
  3. 高效:按需订阅状态,避免不必要的重渲染。

如果你受够了 Redux 的繁琐或 Context 的嵌套,试试 Zustand------ 它会让你发现,React 状态管理可以像用useState一样自然,却能轻松搞定全局共享。

相关推荐
simple_lau33 分钟前
鸿蒙设备如何与低功耗蓝牙设备通讯
前端
啃火龙果的兔子1 小时前
解决 Node.js 托管 React 静态资源的跨域问题
前端·react.js·前端框架
ttyyttemo2 小时前
Compose生命周期---Lifecycle of composables
前端
以身入局2 小时前
FragmentManager 之 addToBackStack 作用
前端·面试
sophie旭2 小时前
《深入浅出react》总结之 10.7 scheduler 异步调度原理
前端·react.js·源码
练习前端两年半2 小时前
Vue3 源码深度剖析:有状态组件的渲染机制与生命周期实现
前端·vue.js
大胖猫L2 小时前
深搜与广搜在 TypeScript 类型递归中的应用
前端·算法
吃饭睡觉打豆豆嘛2 小时前
彻底搞懂前端路由:从 Hash 到 History 的演进与实践
前端·javascript
蛋仔聊测试2 小时前
基于 Playwright(python) 的前端性能测试脚本实现
前端·python
算了吧2 小时前
基于vue3和koa2打造的一款企业级应用框架(建设中)-Elpis
前端·前端框架