Zustand:强大的React 状态管理工具

74 阅读7分钟

写在前面

在 React 应用开发中,状态管理从来不只是“存一个值”那么简单。
从最初的 useState 到自定义 Hooks,再到 Context + useReducer 的组合,我们一直在寻找一种既能保持简洁,又能应对复杂性的方案。尤其当应用开始需要跨组件共享状态、持久化数据、甚至支持调试与扩展时,那些“看似够用”的临时方案,往往会在不经意间变成维护的泥潭。

而今天,我们站在一个更成熟的工具生态之上——Zustand 的出现,不是为了颠覆,而是为了简化那些本不该复杂的逻辑。它不强制你采用单一全局状态树,也不绑架你的架构选择;相反,它用极简的 API,让我们能把精力真正聚焦在业务本身。

接下来,就让我们从一个最朴素的计数器开始,看看 Zustand 是如何优雅地解决“刷新不丢状态”这个经典难题的。

没有zustand 时

在没有 Zustand 的时代,我们通常依靠 自定义 Hooks 来实现组件内的状态管理——这确实是一种简洁、符合 React 设计模式 的方式。

就拿之前的 Todo 小项目来说,我们可能会这样组织代码:

// src/hooks/useTodos.ts
export function useTodos() {
  // 初始化状态,从本地存储读取
  const [todos, setTodos] = useState<Todo[]>(
    () => getStorage<Todo[]>(STORAGE_KEY, [])
  );
  
  // 状态变化时自动保存到本地存储
  useEffect(() => {
    setStorage<Todo[]>(STORAGE_KEY, todos);
  }, [todos]);
  
  // Todo 操作方法
  const addTodo = (title: string) => {
    const newTodo: Todo = { id: +new Date(), title, completed: false };
    setTodos([...todos, newTodo]);
  };
  
//其它省略
  
  // 返回状态和方法
  return { todos, addTodo, toggleTodo, removeTodo };
}

配合一个通用的本地存储工具:

// src/utils/storages.ts
export function getStorage<T>(key: string, defaultValue: T): T {
  const value = localStorage.getItem(key);
  return value ? JSON.parse(value) : defaultValue;
}

export function setStorage<T>(key: string, value: T) {
  localStorage.setItem(key, JSON.stringify(value));
}

在应用入口处使用:

// src/App.tsx
export default function App() {
  const { todos, addTodo, toggleTodo, removeTodo } = useTodos();
  
  return (
    <div>
      <TodoInput onAdd={addTodo} />
      <TodoList 
        todos={todos} 
        onToggle={toggleTodo} 
        onRemove={removeTodo} 
      />
    </div>
  );
}

这样做当然无可厚非:没有引入第三方依赖,逻辑清晰,TypeScript 支持良好,也确实完成了本地持久化的需求。

但是,

当应用逐渐复杂,状态需要跨越多个组件层级、甚至多个页面共享时,我们就不得不考虑将状态“提升”——最常见的做法是引入 React Context。虽然 Context 能解决跨组件通信的问题,但它也带来了新的挑战:

逻辑分散:状态初始化、持久化逻辑、操作方法被拆分在多个文件中,难以集中维护;

模板代码冗余:每个需要持久化的状态几乎都要重复写 useState + useEffect + 存储工具调用;

性能隐患:Context 的更新会让用户重新渲染,除非手动优化(如拆分 Context 或使用 useMemo);

扩展性受限:一旦需要加入中间件(如日志、时间旅行、版本迁移等),自定义 Hook 很难支持。或者说可以支持,但支持方式却没那么“优雅”

更关键的是,这种“每个功能写一个自定义 Hook”的模式,在项目规模扩大后,会迅速演变成大量结构相似但无法复用的代码片段——看似简单,实则隐藏了长期维护的成本。

正因如此,我们才需要一个更先进、更专注的状态管理工具。
它应当天然支持模块化内置持久化能力避免不必要的重渲染,并且让状态逻辑集中、可测试、可扩展
而 Zustand,正是为解决这些问题而生。

zustand

Zustand 是一个极简却功能强大的 React 状态管理库,其核心理念是“每个 Store 负责管理特定的状态”,从而天然支持多状态管理。与传统全局单一状态树不同,Zustand 鼓励将状态按功能拆分为多个独立的 Store,彼此解耦、职责清晰,极大提升了代码的可维护性和可测试性。

image.png

我们可以轻松地在 Vite 脚手架生成的经典页面基础上,引入 Zustand 来增强交互能力。比如,为页面添加 +1、-1 和 reset 按钮,并让这些操作的状态在页面刷新后依然保留——这正是 Zustand 结合其内置中间件 persist 所擅长的场景。

以下是一个典型的使用示例:

image.png

接下来我会用zustand来增加下面的+1 -1 reset按钮 多余的样式删掉了

import {
  useState
} from 'react';
import { useCounterStore } from './store/counter';
import { useTodoStore } from './store/todo';

function App() {
  const { 
      count, 
      increment,
      decrement,
      reset,
  } = useCounterStore();
  return (
    <>
        <button onClick={increment}>
          count is {count}
        </button>
        
        <button onClick={decrement}>
        减1
        </button>
        
        <button onClick={reset}>
        reset
        </button>
    </>
  )
}

export default App

对应的 Store 定义如下:

import { create }from 'zustand';
import { persist } from 'zustand/middleware';
interface CounterState {
    count: number;
    increment: () => void;
    decrement:() => void;
    reset:() => void;
}
export const useCounterStore = create<CounterState>()(
    persist((set) => ({
    count: 0,
    increment: () => set((state) =>({count: state.count + 1})),
    decrement:() => set((state) =>({count: state.count - 1})),
    reset:() => set({count : 0})

}),
{
    name:'counter',
})
)

这里要介绍下 persist:

persist 是 Zustand 提供的一个内置中间件,用于将状态持久化到本地存储

如 localStorage 或 sessionStorage。通过使用 persist,即使用户刷新页面、关闭浏览器再重新打开,应用的状态(例如计数器的数值)也不会丢失。

它的工作原理是自动在状态发生变化时将其序列化为 JSON 字符串并保存到指定的存储介质中并在页面加载时从存储中读取对应的数据,反序列化后作为 Store 的初始状态。这一过程对开发者几乎是透明的,极大地简化了状态持久化的实现。

在使用 persist 时,一个关键的配置参数是 name,例如 { name: 'counter' }。这个 name 会作为本地存储中的键名(key),实际数据会被保存在 localStorage.getItem('counter') 中。设置唯一的 name 

这样做不仅能标识当前 Store 的数据,还能确保多个 Store 之间的数据彼此隔离,避免因键名冲突导致状态覆盖或读取错误。

persist 的工作流程大致如下:当页面加载时,中间件会尝试从 localStorage(或其他指定存储)中读取以 name 为键的数据;如果存在,则通过 JSON.parse 将其解析为 JavaScript 对象,并用作 Store 的初始状态;随后,每当状态发生变更,persist 会自动调用 JSON.stringify 将最新状态序列化,并重新写入存储。整个过程形成了一个闭环,实现了“自动保存 + 自动恢复”的效果。

实际存储的内容通常是一个包含 state 和 version 字段的 JSON 对象,例如 {"state":{"count":5},"version":0}。这意味着即使页面刷新,Store 也能准确还原之前的状态(如 count 仍为 5)。

除了 namepersist 还支持多种可选配置:可以通过 storage 指定使用 sessionStorage 而非默认的 localStorage;通过 version 实现版本控制,便于未来进行数据结构迁移;还可以通过 onRehydrateStorage 注册回调函数,在数据从存储中恢复时执行自定义逻辑(如显示加载提示或处理兼容性问题)。

总之,persist 中间件显著增强了 Zustand 的实用性,而 name 参数则是其实现可靠持久化的基石。有了它,开发者可以轻松实现“刷新不丢状态”的用户体验,让前端应用更加健壮和用户友好。

总结

回过头看,从自定义 Hook 到 Zustand,并不是堆砌技术,而是对可维护性、可扩展性和开发者体验的持续追求
Zustand 之所以“聪明”,不在于它功能繁多,而在于它精准地切中了现代前端状态管理的核心痛点:解耦、持久、高效、可测。通过 persist 中间件,它把原本需要手动处理的存储逻辑封装得几乎“隐形”;通过独立 Store 的设计,它让每个功能模块的状态边界清晰、互不干扰;而这一切,都建立在极低的学习成本和零样板代码的基础上。

更重要的是,Zustand 甘愿做一个克制的工具——这恰恰是它能在众多状态管理库中脱颖而出的原因。

所以,当你下次面对一个需要跨页面共享、本地持久化、甚至未来可能加入时间旅行或日志追踪的状态场景时,不妨试试 Zustand。