Zustand 状态管理入门
用过 Redux 的小伙伴一定都深有体会:它的使用流程相对来说比较繁琐。首先需要定义一个个动作(action),然后编写一个又长又复杂的 reducer 函数来处理这些动作,接着在组件中通过 dispatch 主动触发这些动作,才能更新状态。而且,想要在组件中使用状态时,还得通过 connect 或 useSelector 一个个引入,使用起来不够直观。再加上 Redux 通常需要拆分出多个文件夹来管理 action、reducer、type、saga、store 等内容,整体文件结构显得非常复杂,对于新手或者中小型项目来说,学习和维护成本都比较高。为了解决这些问题,我们引入一种更轻量、更简单、更适合 React 项目的状态管理方案 —— Zustand。
一、Zustand 是什么?为什么选择它?
Zustand 是一个轻量、灵活、易上手的状态管理库,专为 React 设计,目标是让状态管理变得简单又高效,非常适合希望快速开发、减少样板代码的 React 开发者。
🌟 Zustand 的核心优势:
| 特点 | 描述 |
|---|---|
| 🧱 极简 API | 只需要几行代码就能创建 store |
| 🔧 无需样板代码 | 不需要写 actions、reducers、dispatch |
| ✨ 状态直接更新 | 就像操作普通对象一样方便 |
| ⚡ 自动优化渲染 | 只在相关状态变化时更新组件 |
| 🧩 支持异步操作 | 可以轻松处理 API 请求等异步逻辑 |
💡 小贴士:如果你觉得 Redux 太复杂、太繁琐,Zustand 是一个非常好的替代方案,尤其适合中小型项目。
二、项目初始化与安装
我们先创建一个 React 项目,并安装 Zustand。
1. 创建 React 项目(已有项目可跳过)
npx create-react-app zustand-demo
cd zustand-demo
2. 安装 Zustand
npm install zustand
三、创建 Zustand
(1)创建store
create() :是 Zustand 提供的核心函数,用于创建一个 store。useCounterStore中定义了多种更新数字的方法。
import create from 'zustand'
// 创建store(包含状态和方法)
const useCounterStore = create((set) => ({
count: 0, // 初始状态
name: 'Zustand Demo',
// 定义更新方法
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
updateName: (newName) => set({ name: newName }),
reset: () => set({ count: 0, name: 'Reset Done' })
}))
(2)更新state
- 一次性拿到所有数据(比如
fullStore),但不推荐,因为这样组件会响应所有数据变化,效率低。 - 只拿你需要的数据(比如
count、name),这样组件只在这些数据变化时才更新,效率更高。
function Counter() {
// 访问整个store(不推荐,会响应所有状态变化)
const fullStore = useCounterStore()
// 推荐:选择性订阅需要的状态/方法
const { count, name } = useCounterStore(state => ({
count: state.count,
name: state.name
}))
const increment = useCounterStore(state => state.increment)
const reset = useCounterStore(state => state.reset)
Zustand 的 store 中,状态和操作状态的方法是写在一起的,不需要像 Redux 那样拆分成多个文件。
💡 小贴士:Zustand 的 store 是一个React Hook,你可以直接在组件中调用它来获取状态和方法。
四、在组件中使用 Zustand
Zustand 的一大优点就是使用起来非常简单。我们只需要导入 store,就可以直接使用状态和方法。
✅ 示例 1:显示待办事项列表
使用步骤详解:
-
导入 store
import useTodoStore from './store/todoStore' -
使用 store 中的状态和方法
const { todos, deleteTodo, toggleTodo } = useTodoStore() -
渲染待办事项列表
return ( <ul> {todos.map(todo => ( <li key={todo.id}> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }} onClick={() => toggleTodo(todo.id)} > {todo.text} </span> <button onClick={() => deleteTodo(todo.id)}>删除</button> </li> ))} </ul> )
🧠 小贴士:Zustand 使用小技巧
- ✅ 状态和方法都在同一个 Hook 中:不需要像 Redux 那样分别导入 actions 和 selectors。
- ✅ 组件只订阅使用到的状态:Zustand 会自动优化渲染,只有相关状态变化时才会更新组件。
- ✅ 可以随时在任何地方访问状态:比如
useTodoStore.getState().todos。 - ✅ 支持异步操作:你可以在 store 中轻松处理 API 请求。
五、Zustand 的高级用法
在掌握了 Zustand 的基本使用之后,我们可以进一步了解它的一些高级用法,帮助我们更好地优化性能、扩展功能,让状态管理更加灵活高效。
✅ 1. 选择性订阅状态,提升组件性能
Zustand 的一个优势是:组件只会因它关心的状态变化而重新渲染,这可以有效提升性能。
我们可以通过传入一个选择器函数来只订阅我们关心的状态:
function TodoCounter() {
const todoCount = useTodoStore(state => state.todos.length)
return <div>总待办事项: {todoCount}</div>
}
上面这个组件只关心 todos 的长度,所以只有当 todos 发生变化时,这个组件才会重新渲染,而不是每次整个 store 更新都重新渲染。
🧠 小贴士:如果直接使用
const store = useTodoStore(),组件会在 store 中任何状态变化时都重新渲染,效率较低。
✅ 2. 在组件外部访问和修改状态
Zustand 不仅能在 React 组件中使用,还可以在非组件文件中访问和修改状态,比如在工具函数、服务文件中使用。
// 获取当前状态
const currentTodos = useTodoStore.getState().todos
// 修改状态
useTodoStore.setState({ todos: [] })
这在处理异步逻辑、封装业务逻辑时非常有用。比如你可以写一个 todoService.js 文件来统一管理所有的 todo 操作。
✅ 3. 支持异步操作,轻松处理网络请求
Zustand 并不排斥异步操作,你可以在 store 中直接定义异步方法,比如请求数据、保存数据等:
const useTodoStore = create((set) => ({
todos: [],
loading: false,
fetchTodos: async () => {
set({ loading: true })
const response = await fetch('/api/todos')
const todos = await response.json()
set({ todos, loading: false })
}
}))
然后你可以在组件中这样调用:
function TodoList() {
const { fetchTodos, todos, loading } = useTodoStore()
useEffect(() => {
fetchTodos()
}, [])
if (loading) return <div>加载中...</div>
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
)
}
这样,数据请求和状态更新就统一管理在 store 中,逻辑清晰,便于维护。
✅ 4. 使用中间件扩展功能(可选)
虽然这不属于本节重点,但值得一提的是,Zustand 支持中间件,比如:
zustand/middleware:支持日志、持久化、时间旅行调试等zustand/devtools:支持 Redux DevTools 调试
你可以像这样开启 DevTools 支持:
import { devtools } from 'zustand/middleware'
const useStore = create(devtools((set) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 }))
})))