Appearance
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边界
- 复杂业务逻辑
- 需要丰富中间件支持
- 对包大小敏感的项目
两者都是优秀的状态管理库,选择取决于项目需求、团队偏好和开发方式。