Skip to content

Valtio与Zustand对比

概述

Valtio和Zustand都是现代React状态管理库,它们都致力于简化状态管理,但采用了不同的设计理念。Valtio基于Proxy实现可变更新,而Zustand基于不可变更新。本文深入对比这两个库的特点、用法和适用场景。

核心理念对比

Valtio - 可变更新

Valtio允许直接修改状态对象,通过Proxy自动追踪变化:

jsx
import { proxy, useSnapshot } from 'valtio';

// Valtio方式
const state = proxy({
  count: 0,
  user: { name: 'John', age: 30 }
});

// 直接修改
state.count++;
state.user.name = 'Jane';
state.user.age = 31;

// 在组件中使用
function Counter() {
  const snap = useSnapshot(state);
  return (
    <div>
      <p>Count: {snap.count}</p>
      <p>User: {snap.user.name}, Age: {snap.user.age}</p>
      <button onClick={() => state.count++}>Increment</button>
    </div>
  );
}

Zustand - 不可变更新

Zustand使用函数式更新模式,但内部集成了Immer:

jsx
import { create } from 'zustand';

// Zustand方式
const useStore = create((set) => ({
  count: 0,
  user: { name: 'John', age: 30 },
  
  increment: () => set((state) => ({ count: state.count + 1 })),
  updateUser: (name, age) => set((state) => ({
    user: { ...state.user, name, age }
  })),
  
  // 或使用Immer
  incrementWithImmer: () => set((state) => {
    state.count++; // Immer处理不可变性
  })
}));

// 在组件中使用
function Counter() {
  const { count, user, increment, updateUser } = useStore();
  return (
    <div>
      <p>Count: {count}</p>
      <p>User: {user.name}, Age: {user.age}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={() => updateUser('Jane', 31)}>Update User</button>
    </div>
  );
}

API对比

状态创建

jsx
// Valtio - 直接创建代理对象
const valtioState = proxy({
  todos: [],
  filter: 'all',
  user: null
});

// Zustand - 使用create函数
const useZustandStore = create((set, get) => ({
  todos: [],
  filter: 'all',
  user: null,
  
  // 需要定义actions
  addTodo: (todo) => set((state) => ({ 
    todos: [...state.todos, todo] 
  })),
  setFilter: (filter) => set({ filter }),
  setUser: (user) => set({ user })
}));

状态读取

jsx
// Valtio - 使用useSnapshot
function ValtioComponent() {
  const snap = useSnapshot(valtioState);
  
  return <div>{snap.todos.length}</div>;
}

// Zustand - 使用selector
function ZustandComponent() {
  const todoCount = useZustandStore((state) => state.todos.length);
  
  return <div>{todoCount}</div>;
}

状态更新

jsx
// Valtio - 直接修改
valtioState.todos.push({ id: 1, text: 'New todo' });
valtioState.filter = 'completed';
valtioState.user = { name: 'Alice' };

// Zustand - 调用actions
const { addTodo, setFilter, setUser } = useZustandStore.getState();
addTodo({ id: 1, text: 'New todo' });
setFilter('completed');
setUser({ name: 'Alice' });

性能对比

重渲染优化

jsx
// Valtio - 自动优化,只有使用的字段变化才重渲染
function ValtioUserName() {
  const snap = useSnapshot(valtioState);
  // 只有user.name变化才重渲染
  return <div>{snap.user?.name}</div>;
}

function ValtioTodoCount() {
  const snap = useSnapshot(valtioState);
  // 只有todos数组变化才重渲染
  return <div>{snap.todos.length}</div>;
}

// Zustand - 需要精确的selector
function ZustandUserName() {
  const userName = useZustandStore((state) => state.user?.name);
  return <div>{userName}</div>;
}

function ZustandTodoCount() {
  const todoCount = useZustandStore((state) => state.todos.length);
  return <div>{todoCount}</div>;
}

性能测试案例

jsx
// 测试:大量组件订阅不同状态片段
const testState = {
  // Valtio
  valtio: proxy({
    counters: Array.from({ length: 1000 }, (_, i) => ({ id: i, value: 0 }))
  }),
  
  // Zustand
  zustand: create((set) => ({
    counters: Array.from({ length: 1000 }, (_, i) => ({ id: i, value: 0 })),
    updateCounter: (id, value) => set((state) => ({
      counters: state.counters.map(counter => 
        counter.id === id ? { ...counter, value } : counter
      )
    }))
  }))
};

// Valtio组件 - 自动优化
function ValtioCounter({ id }) {
  const snap = useSnapshot(testState.valtio);
  const counter = snap.counters[id];
  
  return (
    <div>
      {counter.value}
      <button onClick={() => testState.valtio.counters[id].value++}>
        +
      </button>
    </div>
  );
}

// Zustand组件 - 需要优化selector
function ZustandCounter({ id }) {
  const counter = testState.zustand((state) => 
    state.counters.find(c => c.id === id)
  );
  const updateCounter = testState.zustand((state) => state.updateCounter);
  
  return (
    <div>
      {counter.value}
      <button onClick={() => updateCounter(id, counter.value + 1)}>
        +
      </button>
    </div>
  );
}

使用模式对比

简单状态管理

jsx
// Valtio - 更简洁
const simpleState = proxy({ count: 0 });

function SimpleValtio() {
  const snap = useSnapshot(simpleState);
  return (
    <button onClick={() => simpleState.count++}>
      Count: {snap.count}
    </button>
  );
}

// Zustand - 需要更多设置
const useSimpleStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 }))
}));

function SimpleZustand() {
  const { count, increment } = useSimpleStore();
  return (
    <button onClick={increment}>
      Count: {count}
    </button>
  );
}

复杂状态管理

jsx
// Valtio - 复杂嵌套状态
const complexState = proxy({
  users: new Map(),
  posts: [],
  ui: {
    theme: 'light',
    modals: {
      userEdit: { open: false, userId: null },
      postCreate: { open: false }
    }
  },
  
  // 计算属性
  get activeUsers() {
    return Array.from(this.users.values()).filter(user => user.active);
  }
});

// 直接操作
complexState.users.set(1, { id: 1, name: 'John', active: true });
complexState.ui.modals.userEdit.open = true;
complexState.ui.modals.userEdit.userId = 1;

// Zustand - 结构化actions
const useComplexStore = create((set, get) => ({
  users: new Map(),
  posts: [],
  ui: {
    theme: 'light',
    modals: {
      userEdit: { open: false, userId: null },
      postCreate: { open: false }
    }
  },
  
  // Actions
  addUser: (user) => set((state) => {
    const newUsers = new Map(state.users);
    newUsers.set(user.id, user);
    return { users: newUsers };
  }),
  
  openUserEditModal: (userId) => set((state) => ({
    ui: {
      ...state.ui,
      modals: {
        ...state.ui.modals,
        userEdit: { open: true, userId }
      }
    }
  })),
  
  closeUserEditModal: () => set((state) => ({
    ui: {
      ...state.ui,
      modals: {
        ...state.ui.modals,
        userEdit: { open: false, userId: null }
      }
    }
  })),
  
  // 计算属性需要手动实现
  getActiveUsers: () => {
    const { users } = get();
    return Array.from(users.values()).filter(user => user.active);
  }
}));

异步操作

jsx
// Valtio - 直接在异步函数中修改
const asyncState = proxy({
  data: null,
  loading: false,
  error: null
});

async function fetchData() {
  asyncState.loading = true;
  asyncState.error = null;
  
  try {
    const response = await fetch('/api/data');
    asyncState.data = await response.json();
  } catch (error) {
    asyncState.error = error.message;
  } finally {
    asyncState.loading = false;
  }
}

// Zustand - 使用set函数
const useAsyncStore = create((set) => ({
  data: null,
  loading: false,
  error: null,
  
  fetchData: async () => {
    set({ loading: true, error: null });
    
    try {
      const response = await fetch('/api/data');
      const data = await response.json();
      set({ data, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  }
}));

TypeScript支持对比

Valtio TypeScript

typescript
import { proxy, useSnapshot } from 'valtio';

interface User {
  id: number;
  name: string;
  email: string;
}

interface AppState {
  users: User[];
  currentUser: User | null;
  loading: boolean;
}

// 类型推导完美
const state = proxy<AppState>({
  users: [],
  currentUser: null,
  loading: false
});

// 自动类型推导
function ValtioTypedComponent() {
  const snap = useSnapshot(state);
  
  // snap.users 自动推导为 User[]
  // snap.currentUser 自动推导为 User | null
  return (
    <div>
      {snap.currentUser?.name}
      {snap.users.length}
    </div>
  );
}

Zustand TypeScript

typescript
import { create } from 'zustand';

interface User {
  id: number;
  name: string;
  email: string;
}

interface UserStore {
  users: User[];
  currentUser: User | null;
  loading: boolean;
  
  setUsers: (users: User[]) => void;
  setCurrentUser: (user: User | null) => void;
  setLoading: (loading: boolean) => void;
}

const useUserStore = create<UserStore>((set) => ({
  users: [],
  currentUser: null,
  loading: false,
  
  setUsers: (users) => set({ users }),
  setCurrentUser: (currentUser) => set({ currentUser }),
  setLoading: (loading) => set({ loading })
}));

function ZustandTypedComponent() {
  const { users, currentUser } = useUserStore();
  
  return (
    <div>
      {currentUser?.name}
      {users.length}
    </div>
  );
}

生态系统对比

Valtio生态

jsx
// 持久化
import { subscribeKey } from 'valtio/utils';

const persistentState = proxy({
  settings: JSON.parse(localStorage.getItem('settings') || '{}')
});

subscribeKey(persistentState, 'settings', (settings) => {
  localStorage.setItem('settings', JSON.stringify(settings));
});

// 派生状态
import { derive } from 'valtio/utils';

const derivedState = derive({
  doubled: (get) => get(state).count * 2,
  isEven: (get) => get(state).count % 2 === 0
});

// 开发工具
import { devtools } from 'valtio/utils';

const devState = devtools(state, { name: 'MyApp' });

Zustand生态

jsx
// 持久化
import { persist } from 'zustand/middleware';

const usePersistStore = create(
  persist(
    (set) => ({
      settings: {},
      updateSettings: (settings) => set({ settings })
    }),
    { name: 'settings-storage' }
  )
);

// 开发工具
import { devtools } from 'zustand/middleware';

const useDevStore = create(
  devtools((set) => ({
    count: 0,
    increment: () => set((state) => ({ count: state.count + 1 }))
  }))
);

// Immer集成
import { immer } from 'zustand/middleware/immer';

const useImmerStore = create(
  immer((set) => ({
    nested: { deep: { value: 0 } },
    updateNested: (value) => set((state) => {
      state.nested.deep.value = value;
    })
  }))
);

实战案例对比

案例1:购物车应用

jsx
// Valtio实现
const valtioCart = proxy({
  items: [],
  
  get total() {
    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  },
  
  get itemCount() {
    return this.items.reduce((sum, item) => sum + item.quantity, 0);
  }
});

function addToValtioCart(product) {
  const existingItem = valtioCart.items.find(item => item.id === product.id);
  
  if (existingItem) {
    existingItem.quantity++;
  } else {
    valtioCart.items.push({ ...product, quantity: 1 });
  }
}

function ValtioCart() {
  const snap = useSnapshot(valtioCart);
  
  return (
    <div>
      <p>Items: {snap.itemCount}</p>
      <p>Total: ${snap.total}</p>
      {snap.items.map(item => (
        <div key={item.id}>
          {item.name} x {item.quantity}
          <button onClick={() => item.quantity++}>+</button>
          <button onClick={() => item.quantity--}>-</button>
        </div>
      ))}
    </div>
  );
}

// Zustand实现
const useCartStore = create((set, get) => ({
  items: [],
  
  addItem: (product) => set((state) => {
    const existingItem = state.items.find(item => item.id === product.id);
    
    if (existingItem) {
      return {
        items: state.items.map(item =>
          item.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        )
      };
    } else {
      return {
        items: [...state.items, { ...product, quantity: 1 }]
      };
    }
  }),
  
  updateQuantity: (id, quantity) => set((state) => ({
    items: state.items.map(item =>
      item.id === id ? { ...item, quantity } : item
    )
  })),
  
  get total() {
    return get().items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  },
  
  get itemCount() {
    return get().items.reduce((sum, item) => sum + item.quantity, 0);
  }
}));

function ZustandCart() {
  const { items, updateQuantity } = useCartStore();
  const total = useCartStore((state) => state.total());
  const itemCount = useCartStore((state) => state.itemCount());
  
  return (
    <div>
      <p>Items: {itemCount}</p>
      <p>Total: ${total}</p>
      {items.map(item => (
        <div key={item.id}>
          {item.name} x {item.quantity}
          <button onClick={() => updateQuantity(item.id, item.quantity + 1)}>+</button>
          <button onClick={() => updateQuantity(item.id, item.quantity - 1)}>-</button>
        </div>
      ))}
    </div>
  );
}

案例2:表单状态管理

jsx
// Valtio表单
const valtioForm = proxy({
  data: {
    name: '',
    email: '',
    message: ''
  },
  errors: {},
  touched: {},
  
  get isValid() {
    return Object.keys(this.errors).length === 0;
  },
  
  get isDirty() {
    return Object.keys(this.touched).length > 0;
  }
});

function validateValtioField(field, value) {
  if (!value) {
    valtioForm.errors[field] = `${field} is required`;
  } else {
    delete valtioForm.errors[field];
  }
}

function ValtioForm() {
  const snap = useSnapshot(valtioForm);
  
  return (
    <form>
      <input
        value={snap.data.name}
        onChange={(e) => {
          valtioForm.data.name = e.target.value;
          valtioForm.touched.name = true;
          validateValtioField('name', e.target.value);
        }}
      />
      {snap.errors.name && <span>{snap.errors.name}</span>}
      
      <button disabled={!snap.isValid}>Submit</button>
    </form>
  );
}

// Zustand表单
const useFormStore = create((set, get) => ({
  data: {
    name: '',
    email: '',
    message: ''
  },
  errors: {},
  touched: {},
  
  setField: (field, value) => {
    set((state) => ({
      data: { ...state.data, [field]: value },
      touched: { ...state.touched, [field]: true }
    }));
    
    // 验证
    get().validateField(field, value);
  },
  
  validateField: (field, value) => {
    set((state) => {
      const newErrors = { ...state.errors };
      
      if (!value) {
        newErrors[field] = `${field} is required`;
      } else {
        delete newErrors[field];
      }
      
      return { errors: newErrors };
    });
  },
  
  isValid: () => Object.keys(get().errors).length === 0,
  isDirty: () => Object.keys(get().touched).length > 0
}));

function ZustandForm() {
  const { data, errors, setField, isValid } = useFormStore();
  
  return (
    <form>
      <input
        value={data.name}
        onChange={(e) => setField('name', e.target.value)}
      />
      {errors.name && <span>{errors.name}</span>}
      
      <button disabled={!isValid()}>Submit</button>
    </form>
  );
}

选择指南

选择Valtio的场景

jsx
// 1. 简单直观的状态修改
const simpleState = proxy({ count: 0, name: 'John' });
simpleState.count++; // 直接修改

// 2. 复杂嵌套数据结构
const nestedState = proxy({
  user: {
    profile: { name: '', avatar: '' },
    settings: { theme: 'light', notifications: true }
  }
});
nestedState.user.profile.name = 'Alice'; // 直接修改深层数据

// 3. 原型开发和快速迭代
// 不需要预先定义actions,可以随时添加新字段

// 4. 喜欢命令式编程风格
function updateUserProfile(updates) {
  Object.assign(state.user.profile, updates);
}

选择Zustand的场景

jsx
// 1. 明确的状态管理边界
const useUserStore = create((set) => ({
  user: null,
  // 明确的API
  setUser: (user) => set({ user }),
  clearUser: () => set({ user: null })
}));

// 2. 团队协作和代码可维护性
// Actions提供了明确的API契约

// 3. 复杂的业务逻辑
const useOrderStore = create((set, get) => ({
  orders: [],
  
  processOrder: async (order) => {
    // 复杂的业务逻辑
    set({ processing: true });
    
    try {
      const result = await processOrderAPI(order);
      set((state) => ({
        orders: [...state.orders, result],
        processing: false
      }));
    } catch (error) {
      set({ error: error.message, processing: false });
    }
  }
}));

// 4. 需要中间件支持
const usePersistStore = create(
  persist(
    devtools((set) => ({
      settings: {},
      updateSettings: (settings) => set({ settings })
    }))
  )
);

性能和包大小对比

包大小

bash
# Valtio
valtio: ~3KB (gzipped)
valtio/utils: ~2KB (gzipped)

# Zustand  
zustand: ~1KB (gzipped)
zustand/middleware: ~2KB (gzipped)

运行时性能

jsx
// 性能测试场景
const ITEM_COUNT = 10000;

// Valtio - 自动优化,但Proxy有overhead
const valtioItems = proxy({
  items: Array.from({ length: ITEM_COUNT }, (_, i) => ({ id: i, value: 0 }))
});

// Zustand - 手动优化,但更直接
const useItemsStore = create((set) => ({
  items: Array.from({ length: ITEM_COUNT }, (_, i) => ({ id: i, value: 0 })),
  updateItem: (id, value) => set((state) => ({
    items: state.items.map(item => 
      item.id === id ? { ...item, value } : item
    )
  }))
}));

// 结果:
// - Valtio在大量小更新时性能更好(自动批处理)
// - Zustand在大量数据读取时性能更好(没有Proxy开销)

迁移指南

从Zustand迁移到Valtio

jsx
// Zustand代码
const useStore = create((set) => ({
  count: 0,
  user: null,
  increment: () => set((state) => ({ count: state.count + 1 })),
  setUser: (user) => set({ user })
}));

function ZustandComponent() {
  const { count, user, increment, setUser } = useStore();
  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>+</button>
    </div>
  );
}

// 迁移到Valtio
const state = proxy({
  count: 0,
  user: null
});

function ValtioComponent() {
  const snap = useSnapshot(state);
  return (
    <div>
      <p>{snap.count}</p>
      <button onClick={() => state.count++}>+</button>
    </div>
  );
}

从Valtio迁移到Zustand

jsx
// Valtio代码
const state = proxy({
  todos: [],
  filter: 'all'
});

// 直接修改
state.todos.push({ id: 1, text: 'New todo' });
state.filter = 'completed';

// 迁移到Zustand
const useTodoStore = create((set) => ({
  todos: [],
  filter: 'all',
  
  addTodo: (todo) => set((state) => ({
    todos: [...state.todos, todo]
  })),
  setFilter: (filter) => set({ filter })
}));

// 调用actions
const { addTodo, setFilter } = useTodoStore.getState();
addTodo({ id: 1, text: 'New todo' });
setFilter('completed');

最佳实践总结

Valtio最佳实践

jsx
// 1. 使用操作函数封装复杂逻辑
const userActions = {
  login(credentials) {
    state.loading = true;
    // 登录逻辑
  },
  
  logout() {
    state.user = null;
    state.token = null;
  }
};

// 2. 利用计算属性
const state = proxy({
  items: [],
  
  get filteredItems() {
    return this.items.filter(item => item.active);
  }
});

// 3. 谨慎使用ref()避免不必要的代理化
import { ref } from 'valtio';

const state = proxy({
  expensiveObject: ref(largeImmutableObject)
});

Zustand最佳实践

jsx
// 1. 合理拆分store
const useAuthStore = create((set) => ({ /* 认证相关 */ }));
const useDataStore = create((set) => ({ /* 数据相关 */ }));

// 2. 使用中间件
const useStore = create(
  devtools(
    persist(
      (set) => ({ /* store逻辑 */ }),
      { name: 'app-storage' }
    )
  )
);

// 3. 优化selector
const Component = () => {
  // 好:只选择需要的数据
  const user = useStore((state) => state.user);
  
  // 避免:选择整个store
  // const store = useStore();
};

总结

Valtio和Zustand各有优势:

Valtio适合:

  • 快速原型开发
  • 简单直观的状态修改
  • 复杂嵌套数据结构
  • 喜欢命令式编程风格
  • 小团队或个人项目

Zustand适合:

  • 大型团队协作
  • 明确的API边界
  • 复杂业务逻辑
  • 需要丰富中间件支持
  • 对包大小敏感的项目

两者都是优秀的状态管理库,选择取决于项目需求、团队偏好和开发方式。