"状态管理这事儿,就像谈恋爱,简单的才是最美的" 💕
前言:为什么要用状态管理?
在现代前端开发中,我们已经进入了一个全新的时代:UI组件 + 全局应用状态管理。想象一下,如果你的React应用是一个大家庭,那么各个组件就像家庭成员,他们需要共享一些信息(比如用户登录状态、购物车数据等)。
小项目可能用不着store,就像单身狗不需要考虑家庭关系🐶。但中大型项目嘛,react-router-dom + zustand 这对CP必须安排上!把所有组件状态收归中央统一管理,这样才不会乱套。
Zustand是什么?
Zustand是一个轻巧、hooks化的状态管理库。相比Redux的繁文缛节,Zustand就像是状态管理界的"小清新":
- 轻量级:体积小,性能好
- Hooks化:完美契合现代React开发
- 简单易用:告别action、reducer、dispatch的复杂套路
对于习惯了 useContext + useReducer + React.createContext 这套组合拳的同学,Zustand绝对是你的菜!
项目结构一览
在开始撸代码之前,先看看我们这个Zustand全家桶项目的整体结构:
zustand-demo/
├── src/
│ ├── api/ # API接口层
│ │ ├── config.js # axios配置
│ │ └── repo.js # GitHub仓库相关接口
│ ├── components/ # 组件目录
│ │ ├── Counter/ # 计数器组件
│ │ │ └── index.jsx
│ │ ├── TodoList/ # 待办事项组件
│ │ │ └── index.jsx
│ │ └── RepoList/ # 仓库列表组件
│ │ └── index.jsx
│ ├── store/ # 状态管理中心
│ │ ├── count.js # 计数器状态
│ │ ├── todos.js # 待办事项状态
│ │ └── repos.js # 仓库数据状态
│ ├── App.jsx # 主应用组件
│ ├── App.css # 应用样式
│ ├── index.css # 全局样式
│ └── main.jsx # 应用入口
├── vite.config.js # Vite配置
└── package.json # 项目依赖
这个结构遵循了现代React项目的最佳实践:
- api层:统一管理所有接口请求
- components:按功能模块组织组件
- store:模块化的状态管理,每个功能一个store
- 样式分离:全局样式和组件样式分开管理
搭建项目基础架构
首先,我们用Vite创建一个React项目。Vite配置简洁明了:
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})
项目入口也很标准:
// main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
实战一:计数器Store - Zustand的Hello World
来看看Zustand的第一个store,经典的计数器:
// store/count.js
import { create } from 'zustand'
// 用create创建一个store,这就是存状态的地方
export const useCounterStore = create((set) => ({
// 状态数据
count: 0,
// 修改状态的方法,类似reducer函数
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
这里的set函数就是Zustand的核心,它负责更新状态。相比Redux需要写action、reducer、dispatch一大堆,这里直接定义方法就行了!
然后在组件中使用:
// components/Counter/index.jsx
import { useCounterStore } from '../../store/count'
const Counter = () => {
// 从store中解构出需要的状态和方法
const { count, increment, decrement } = useCounterStore()
return (
<>
Count {count}
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</>
)
}
export default Counter
是不是感觉比Redux清爽多了?没有Provider包裹,没有useDispatch,直接就能用!
实战二:TodoList Store - 模块化状态管理
接下来我们来个稍微复杂点的:TodoList。这里展示了Zustand在处理数组状态时的优雅:
// store/todos.js
import { create } from 'zustand';
export const useTodosStore = create((set) => ({
// 初始化一些数据
todos: [
{
id: 1,
text: '打王者农药',
completed: false,
}
],
// 添加待办事项
addTodo: (text) => set((state) => ({
todos: [
...state.todos,
{
id: state.todos.length + 1,
text,
completed: false,
}
]
})),
// 切换完成状态
toggleTodo: (id) => set((state) => ({
todos: state.todos.map(
(todo) => todo.id === id ? {
...todo,
completed: !todo.completed
} : todo
)
})),
// 删除待办事项
deleteTodo: (id) => set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id)
}))
}))
组件使用起来也很简单:
// components/TodoList/index.jsx
import { useTodosStore } from '../../store/todos'
const TodoList = () => {
const {
todos,
addTodo,
toggleTodo,
deleteTodo
} = useTodosStore();
return (
<div>
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
)
}
export default TodoList
实战三:异步数据Store - 处理API请求
现在来点高级的:处理异步数据。我们创建一个GitHub仓库列表的store:
首先配置axios:
// api/config.js
import axios from 'axios';
axios.defaults.baseURL = 'https://api.github.com';
export default axios
// api/repo.js
import axios from './config';
export const getRepo = async (name) =>
await axios.get(`/users/${name}/repos`)
export const getRepoInfo = async (name, repo) =>
await axios.get(`/repos/${name}/${repo}`)
export const getRepoList = getRepo;
然后是关键的异步store:
// store/repos.js
import { getRepoList } from '../api/repo'
import { create } from 'zustand'
export const useRepoStore = create((set) => ({
repos: [],
loading: false,
error: null,
fetchRepos: async () => {
// 设置loading状态
set({ loading: true, error: null });
try {
const res = await getRepoList('miao-key')
set({ repos: res.data, loading: false })
} catch (error) {
set({ error: error.message, loading: false})
} finally {
set({ loading: false })
}
}
}))
在组件中使用:
// components/RepoList/index.jsx
import { useEffect } from "react";
import { useRepoStore } from "../../store/repos";
const RepoList = () => {
const {repos, loading, error, fetchRepos} = useRepoStore();
useEffect(() => {
fetchRepos();
}, [])
return (
<>
<ul>
{repos.map(repo => (
<li key={repo.id}>
<a href={repo.html_url} target="_blank" rel="noopener noreferrer">
{repo.name}
</a>
<p>{repo.description || 'No description'}</p>
</li>
))}
</ul>
</>
)
}
export default RepoList;
全家桶整合 - App组件
最后,在主App组件中把所有功能整合起来:
// App.jsx
import { useState } from 'react'
import './App.css'
import Counter from './components/Counter'
import { useCounterStore } from './store/count'
import TodoList from './components/TodoList'
import RepoList from './components/RepoList'
function App() {
const { count } = useCounterStore()
return (
<>
App中的 {count}
<Counter />
<TodoList />
<RepoList />
</>
)
}
export default App
看到了吗?在App组件中,我们可以直接使用useCounterStore获取count状态,这就是Zustand的魅力 - 状态在全局共享,任何组件都可以访问!
Zustand vs Redux:为什么选择Zustand?
| 特性 | Redux | Zustand |
|---|---|---|
| 学习曲线 | 陡峭 | 平缓 |
| 模板代码 | 多 | 少 |
| TypeScript支持 | 需要额外配置 | 开箱即用 |
| 包大小 | 较大 | 很小 |
| 中间件 | 丰富但复杂 | 简单实用 |
实战建议
- 模块化管理:不同功能的状态放在不同的store文件中
- 命名规范:store的hook以
use开头,以Store结尾 - 状态设计:保持状态结构扁平化,避免深层嵌套
- 异步处理:统一处理loading、error状态
- 性能优化:只订阅需要的状态,避免不必要的重渲染
总结
Zustand就像是状态管理界的"瑞士军刀":小巧、实用、功能齐全。它解决了Redux的复杂性问题,又保持了状态管理的专业性。对于现代React开发来说,Zustand无疑是一个优秀的选择。 从简单的计数器到复杂的异步数据处理,Zustand都能游刃有余地应对。它让我们可以专注于业务逻辑,而不是纠结于状态管理的细枝末节。 最重要的是,Zustand让状态管理变得有趣起来!不再是枯燥的模板代码,而是简洁优雅的解决方案。
"代码如诗,状态如歌,Zustand让它们完美和谐" 🎵
如果你也被Zustand的魅力所征服,不妨在项目中试试看吧!相信你会爱上这种简洁而强大的状态管理方式的。