告别Redux!这个1KB的状态管理库让我开发效率提升了300%

315 阅读6分钟

作为一个React开发者,我经历过状态管理的"石器时代"。还记得刚开始用React时,我把所有状态都塞在组件里,结果组件变成了臃肿的"怪物"。后来我尝试了Redux,虽然解决了问题,但写起代码来像是在填表格——action、reducer、store配置一大堆,一个简单的计数器功能都要写半天。

直到我遇见了Zustand,这个德语单词意为"状态",它给我的开发体验带来了翻天覆地的变化。今天我想分享一下,为什么这个不到1KB的库能让我彻底放弃那些笨重的状态管理方案。

为什么选择Zustand?

刚开始接触React时,我和大多数新手一样,把状态一股脑塞进useState里。小项目还好,但随着功能增加,组件间的状态传递变成了噩梦——props drilling(属性钻取)让我的代码变成了"俄罗斯套娃"。

// 早期的组件状态管理噩梦
function Parent() {
  const [count, setCount] = useState(0);
  return <Child count={count} setCount={setCount} />;
}

function Child({ count, setCount }) {
  return <GrandChild count={count} setCount={setCount} />;
}

function GrandChild({ count, setCount }) {
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

后来我学了Redux,确实解决了状态共享问题,但代价是大量的模板代码。创建一个简单的计数器,我需要定义action types、action creators、reducers,最后还要用Provider包裹整个应用。有时候我只是想存个小状态,却像是在为航天飞机写控制程序。

Context API稍微好点,但当我有多个context时,组件树会被一堆Provider包裹,性能也成了问题。就在我准备向状态管理复杂度低头时,Zustand出现了。

Zustand的核心思想很简单:给你一个创建store的hook,这个store可以在任何组件中使用,不需要Provider包裹。它融合了Redux的单一状态树思想和Hooks的简洁性,却没有任何繁琐的配置。

基础篇:创建和使用Store

计数器Store

让我们从一个经典的计数器例子开始。在项目中,我习惯将不同的store放在单独文件中:

// stores/countStore.js
import { create } from 'zustand';

export const useCountStore = create((set) => ({
  count: 0,
  inc: () => set((state) => ({ count: state.count + 1 })),
  dec: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

在组件中使用这个store时,我们可以选择性地获取需要的状态和方法:

// components/Counter.js
import { useCountStore } from '../stores/countStore';

function Counter() {
  const { count, inc, dec } = useCountStore();
  
  return (
    <div>
      <button onClick={dec}>-</button>
      <span>{count}</span>
      <button onClick={inc}>+</button>
    </div>
  );
}

没有Provider,没有connect,没有mapStateToProps,就这么简单。更棒的是,Zustand会自动处理更新优化,只有用到的状态变化时组件才会重新渲染。

TodoList Store

对于更复杂的场景,比如Todo应用,我们可以这样组织store:

// stores/todoStore.js
import { create } from 'zustand';

export const useTodoStore = create((set) => ({
  todos: [],
  addTodo: (text) =>
    set((state) => ({
      todos: [...state.todos, { id: Date.now(), 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.js
import { useState } from 'react';
import { useTodoStore } from '../stores/todoStore';

function TodoList() {
  const [input, setInput] = useState('');
  const { todos, addTodo, toggleTodo, deleteTodo } = useTodoStore();

  const handleSubmit = (e) => {
    e.preventDefault();
    if (input.trim()) {
      addTodo(input);
      setInput('');
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Add a new todo"
        />
        <button type="submit">Add</button>
      </form>
      <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={() => deleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

进阶篇:异步操作与复杂状态

Zustand真正强大的地方在于处理异步操作和复杂状态逻辑时的简洁性。

异步操作示例

在实际项目中,我们经常需要处理API请求。下面是一个用户数据获取的示例:

// stores/userStore.js
import { create } from 'zustand';
import axios from 'axios';

export const useUserStore = create((set) => ({
  user: null,
  loading: false,
  error: null,
  
  fetchUser: async (userId) => {
    set({ loading: true, error: null });
    try {
      const response = await axios.get(`https://api.example.com/users/${userId}`);
      set({ user: response.data, loading: false });
    } catch (error) {
      set({ 
        error: error.response?.data?.message || error.message, 
        loading: false 
      });
    }
  },
  
  updateUser: async (userId, updates) => {
    set({ loading: true });
    try {
      const response = await axios.put(
        `https://api.example.com/users/${userId}`,
        updates,
        {
          headers: { 'Content-Type': 'application/json' },
        }
      );
      set({ user: response.data, loading: false });
    } catch (error) {
      set({ 
        error: error.response?.data?.message || error.message, 
        loading: false 
      });
    }
  },
}));

在组件中使用:

// components/UserProfile.js
import { useEffect } from 'react';
import { useUserStore } from '../stores/userStore';

function UserProfile({ userId }) {
  const { user, loading, error, fetchUser } = useUserStore();
  
  useEffect(() => {
    fetchUser(userId);
  }, [userId, fetchUser]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!user) return <div>No user found</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>Email: {user.email}</p>
      {/* 其他用户信息 */}
    </div>
  );
}

派生状态与计算属性

Zustand支持在store中定义派生状态(类似于Vue的计算属性):

// stores/todoStore.js
export const useTodoStore = create((set, get) => ({
  todos: [],
  // ...其他action
  
  // 派生状态:已完成todo数量
  get completedCount() {
    return get().todos.filter(todo => todo.completed).length;
  },
  
  // 派生状态:未完成todo数量
  get activeCount() {
    return get().todos.length - get().completedCount;
  },
}));

高级技巧与最佳实践

1. 状态持久化

使用zustand/middleware可以轻松实现状态持久化:

// stores/authStore.js
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

export const useAuthStore = create(
  persist(
    (set) => ({
      token: null,
      user: null,
      login: (token, user) => set({ token, user }),
      logout: () => set({ token: null, user: null }),
    }),
    {
      name: 'auth-storage', // localStorage的key
      getStorage: () => localStorage, // 也可以使用sessionStorage
    }
  )
);

2. 使用Devtools调试

// stores/productStore.js
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import axios from 'axios';

export const useProductStore = create(
  devtools(
    (set) => ({
      products: [],
      loading: false,
      error: null,
      
      fetchProducts: async () => {
        set({ loading: true, error: null });
        try {
          const response = await axios.get('https://api.example.com/products');
          set({ 
            products: response.data, 
            loading: false 
          });
        } catch (error) {
          set({ 
            loading: false,
            error: error.response?.data?.message || error.message
          });
        }
      },
      
      // 可以添加更多产品相关操作
      addProduct: async (newProduct) => {
        set({ loading: true });
        try {
          const response = await axios.post(
            'https://api.example.com/products',
            newProduct
          );
          set(state => ({
            products: [...state.products, response.data],
            loading: false
          }));
        } catch (error) {
          set({ 
            loading: false,
            error: error.response?.data?.message || error.message
          });
        }
      }
    }),
    { name: 'ProductStore' } // Devtools中显示的名称
  )
);

3. 细粒度订阅优化性能

// components/TodoStats.js
import { useTodoStore } from '../stores/todoStore';

function TodoStats() {
  // 只订阅completedCount和activeCount,当其他状态变化时不会重新渲染
  const completedCount = useTodoStore((state) => state.completedCount);
  const activeCount = useTodoStore((state) => state.activeCount);

  return (
    <div>
      <p>Completed: {completedCount}</p>
      <p>Active: {activeCount}</p>
    </div>
  );
}

4. 组合多个Store

对于大型项目,我们可以将store拆分为多个小store,然后在需要时组合使用:

// stores/index.js
export { useCountStore } from './countStore';
export { useTodoStore } from './todoStore';
export { useUserStore } from './userStore';
export { useAuthStore } from './authStore';

对比其他状态管理方案

与Redux对比

  1. 学习曲线:Zustand比Redux简单得多,不需要理解action、reducer、dispatch等概念
  2. 模板代码:Zustand几乎没有模板代码,Redux则需要大量样板代码
  3. 性能:两者都很优秀,但Zustand的细粒度订阅更直观
  4. 中间件:Redux中间件生态更丰富,但Zustand的中间件已经覆盖大部分常见需求

与Context API对比

  1. 渲染优化:Context API在值变化时会重新渲染所有消费者,Zustand只渲染使用变化状态的组件
  2. 使用便捷:Zustand不需要Provider层层包裹,使用更简单
  3. 适用场景:Context适合低频变化的主题/本地化等数据,Zustand适合高频变化的业务状态

实战建议

  1. 按功能划分store:不要创建巨型store,而是按业务功能划分
  2. 合理使用中间件:持久化、devtools等中间件可以显著提升开发体验
  3. 避免过度使用:简单的组件状态仍应使用useState,只有共享状态才放入store
  4. 类型安全:使用TypeScript可以获得更好的开发体验:
// stores/countStore.ts
interface CountState {
  count: number;
  inc: () => void;
  dec: () => void;
  reset: () => void;
}

export const useCountStore = create<CountState>((set) => ({
  count: 0,
  inc: () => set((state) => ({ count: state.count + 1 })),
  dec: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

结语

经过多个项目的实践,Zustand已经成为我React工具箱中的必备品。它完美平衡了简单性和功能性,让状态管理从痛苦变成了乐趣。 当然,不同的场景也要选择不同的工具,如果是大型项目,还是老老实实选择redux吧!