React状态管理新选择:Zustand全家桶开发指南

173 阅读5分钟

为什么需要状态管理?

在现代前端开发中,随着应用复杂度增加,组件状态共享状态逻辑复用成为核心挑战。传统的useContext + useReducer组合虽然有效,但随着项目规模增大,会遇到以下问题:

  1. 上下文嵌套过深(Provider Hell)
  2. 不必要的组件重渲染
  3. 状态逻辑分散难以维护

Zustand应运而生——一个轻量级(仅1.6KB)但功能强大的状态管理库,完美融合了React Hooks的简洁性和Redux的可预测性。

count响应式状态实现全局管理

通常情况下咱们会使用下面的Hooks进行状态管理,但是这样太复杂

useContext + useReducer + React.createContext

咱们可以使用redux/zustand进行简化,今天咱们就来使用下zustand该轻量级状态管理库实现咱们的项目。

count响应式实现

1.项目结构组织

src/
├── store/
│   ├── index.js         # 聚合所有store
│   ├── count.js         # 计数器store
│   ├── todos.js         # 待办事项store
│   └── repos.js         # 仓库store
├── api/
│   ├── config.js        # API配置
│   └── repo.js          # 仓库API
└── components/
    ├── Counter/
    ├── TodoList/
    └── RepoList/

2. store仓库

下面让咱们先安装依赖

// zustand react 状态管理框架
pnpm i zustand

现在需要create从zustand当中引入zustand状态管理,并且在该状态下创建一个useCounterStore钩子函数。

// zustand react 状态管理框架
import {
  create  // 创建一个store 存状态的地方
} from 'zustand';

export const useCounterStore = create(() => ({
    // 对象
}))

在上述代码当中,create当中是括号可以知道在仓库存储的应该为一个对象,因此返回的输出应为一个对象,原来的话,咱们需要将修改count的方法写在reducer当中。但现在咱们就可以直接在自己写的hook函数当中定义方法就可以。

// zustand react 状态管理框架
import {
  create  // 创建一个store 存状态的地方
} from 'zustand';

export const useCounterStore = create((set) => ({
  // 对象
  // 状态
  // 修改状态的方法
  count: 0,
  // reducer 函数 规定状态怎么变
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}))

3.components

首先我们需要明白如果是小型项目的话,有很多地方都可以省略,因为小型项目当中,咱们可能就只是写一个页面。并没有其它的页面可以选择,因此componnents和router可以不用配置。接下来咱们来到components组件下,将咱们需要的count响应式管理呈现在页面当中。

import { useCounterStore } from "../../store/count"
// 来自store

const Counter = () => {
  const {
    count,
    increment,
    decrement
  } = useCounterStore()

  return (
    <>
      Counter {count}
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </>
  )
}


export default Counter

关键点解析

  • create函数创建store,返回一个Hook
  • set函数用于更新状态(类似React的setState)
  • 组件直接使用Hook获取状态和操作,无需Provider包裹

在这里可以看到对比之前使用的Reducer当中,这里的代码使用就简洁了许多,并不需要使用到额外的store库来书写。可以自定义更简洁的函数来代替Reducer当中的action和dispatch,咱们可以拿之前的reducer当中的代码进行对比:

// 状态的正确
// reducers文件中的 todoReducers.js文件当中
function todoReducers(state, action){
  switch(action.type){
    case 'ADD_TODO':
      return [...state,{
        id: Date.now(),
        text: action.text,
        done: false
      }];
    case 'TOGGLE_TODO':
      return state.map(todo => 
        todo.id === action.id ? 
        {...todo, done: !todo.done} : todo
      );
    case 'REMOVE_TODO':
      return state.filter(todo => todo.id !== action.id);
    default:
      return state;
  }
}

export default todoReducers;

// hooks文件下的 useTodos.js文件中
import {
  useReducer
}from 'react';
import todoReducers from '../reducers/todoReducers';
// 参数的默认值
// {todos, } key:value 省略
// `` 模板字符串 
// 解构  [] = [] {} = {}
// 展开运算符, ... rest 运算符
export function useTodos(initial = []){
  const [todos, dispatch] = useReducer(todoReducers, initial);

  const addTodo = text => dispatch({type: 'ADD_TODO', text})
  const toggleTodo = id => dispatch({type: 'TOGGLE_TODO', id})
  const removeTodo = id => dispatch({type: 'REMOVE_TODO',id})

  return {
    todos,
    addTodo,
    toggleTodo,
    removeTodo
  }
}
特性ZustanduseReducer + useContext
是否官方支持❌ 第三方库✅ 官方支持
是否适合全局状态管理✅ 简单方便✅ 可以,但需要 Context
学习成本⭐ 简单,易于上手⭐⭐ 相对复杂
性能表现✅ 高性能,细粒度更新✅ 可优化,但需注意 Context 重渲染
代码量⭐ 代码少,结构清晰⭐⭐ 代码较多
可测试性⭐ 状态和方法集中,便于测试⭐ reducer 是纯函数,可测试性强
中间件/扩展能力✅ 支持插件(如持久化、日志)❌ 需要手动实现
适合场景✅ 中小型项目、快速开发✅ 中大型项目、复杂状态逻辑

实战:构建Zustand全家桶应用

1. TodoList应用开发

store/todos.js

import { create } from 'zustand';

export const useTodosStore = create((set) => ({
  todos: [],
  // 添加待办事项
  addTodo: (text) => set((state) => ({
    todos: [
      ...state.todos,
      { 
        id: Date.now(), // 使用时间戳作为ID
        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.jsx

import { useTodosStore } from "../store/todos";

const TodoList = () => {
  const { todos, addTodo, toggleTodo, deleteTodo } = useTodosStore();

  return (
    <div className="todo-container">
      <ul>
        {todos.map(todo => (
          <li key={todo.id} className={todo.completed ? 'completed' : ''}>
            <span>{todo.text}</span>
            <div>
              <button onClick={() => toggleTodo(todo.id)}>
                {todo.completed ? '撤销' : '完成'}
              </button>
              <button onClick={() => deleteTodo(todo.id)}>删除</button>
            </div>
          </li>
        ))}
      </ul>
      
      <input
        type="text"
        placeholder="添加新任务..."
        onKeyDown={(e) => {
          if (e.key === 'Enter' && e.target.value.trim()) {
            addTodo(e.target.value.trim());
            e.target.value = '';
          }
        }}
      />
    </div>
  );
};

设计亮点

  • 状态与UI分离:所有业务逻辑封装在store中
  • 不可变更新:通过扩展运算符确保状态不可变
  • 自然的事务处理:每个操作都是原子性的

2. 异步数据获取(GitHub仓库列表)

api/config.js

// 配置文件 
import axios from 'axios'

axios.defaults.baseURL = "http://api.github.com"

export default axios

api/repo.js

import axios from './config';
// 配置基础URL

export const getRepos = async (owner, repo) =>
  await axios.get(`/repos/${owner}/${repo}`)


export const getRepoList = async (owner) => 
  await axios.get(`/users/${owner}/repos`) 

store/repos.js

import { create } from 'zustand';
import { getRepoList } from '../api/repo';

export const useReposStore = create((set) => ({
  repos: [],
  loading: false,
  error: null,
  
  // 异步获取仓库数据
  fetchRepos: async (username) => {
    set({ loading: true, error: null });
    try {
      const repos = await getRepoList(username);
      set({ repos, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },
}));

components/RepoList.jsx

import {
  useReposStore
} from '../../store/repos'
import { useEffect } from 'react'

const RepoList = () => {
  const { repos, loading, fetchRepos, error } = useReposStore()
  useEffect(() => {
    fetchRepos()
  }, [])
  if (loading) return <p>loading...</p>
  if (error) return <p>{error}</p>

  return (
    <div>
      <h2>Repo List</h2>
      <ul>
        {
          repos.map(repo => (
            <li key={repo.id}>
              <a href={repo.html_url} target="_blank" rel="noreferrer">
                {repo.name}
              </a>
              <p>{repo.description || 'No description'}</p>
            </li>
          ))
        }
        {
          error && <li>{error}</li>
        }
      </ul>
    </div>
  )
}
export default RepoList

异步处理最佳实践

  1. 状态追踪:设置loading和error状态
  2. 错误边界:捕获并处理可能的异常
  3. 用户体验:提供加载状态和错误反馈
  4. 数据转换:在store中处理数据格式化

可以看到最终咱们实现的页面效果图啦: image.png


总结

graph TD
    A[组件] --> B[Zustand Store]
    B --> C[计数器状态]
    B --> D[待办事项状态]
    B --> E[API数据状态]
    C --> F[计数器组件]
    D --> G[待办列表组件]
    E --> H[仓库列表组件]

Zustand代表了React状态管理的新范式——它平衡了简单性和功能性,让开发者可以专注于业务逻辑而非状态管理框架本身。无论是小型工具还是大型应用,Zustand都能提供优雅的解决方案。