03-状态管理与路由——04. Zustand 状态读取与选择器

📅 2026/6/16 11:53:56
03-状态管理与路由——04. Zustand 状态读取与选择器
04. Zustand 状态读取与选择器一、5W1H 概述维度内容What使用 useStore 和选择器读取状态Why优化性能只在相关状态变化时重渲染When组件需要订阅 store 中的部分状态Where组件内部Who使用 Zustand 的开发者Howconst count useStore(state state.count)二、What - 什么是选择器选择器Selector是一个函数从 store 中提取特定的状态片段。Zustand 使用选择器来优化组件渲染性能。// 选择器函数从完整状态中提取需要的部分 const countSelector (state) state.count; const userSelector (state) state.user; // 使用选择器 const count useStore(countSelector);三、Why - 为什么需要选择器3.1 性能优化不使用选择器时组件会在任何状态变化时重渲染// ❌ 任何状态变化都会导致组件重渲染 const { count, name, age } useStore(); // ✅ 只在 count 变化时重渲染 const count useStore(state state.count);3.2 精确订阅选择器让你只订阅组件真正需要的状态避免不必要的渲染。四、When - 何时使用选择器场景推荐方式说明订阅单个状态使用选择器state state.count订阅多个状态使用选择器返回对象state ({ count: state.count, name: state.name })派生状态在选择器中计算state state.todos.filter(t !t.completed)整个 store不使用选择器性能较差不推荐五、Where - 在哪里使用组件内部自定义 Hook 中任何需要访问 store 的地方// 组件内 function MyComponent() { const count useStore(state state.count); } // 自定义 Hook function useCount() { return useStore(state state.count); }六、Who - 谁需要使用所有使用 Zustand 的开发者都应该掌握选择器的使用。七、How - 如何使用选择器7.1 基础选择器import { create } from zustand; const useStore create((set) ({ count: 0, name: 张三, age: 25, increment: () set((state) ({ count: state.count 1 })), setName: (name) set({ name }) })); // 订阅单个状态 function CountDisplay() { const count useStore(state state.count); return div计数: {count}/div; } // 订阅多个状态分别订阅 function UserInfo() { const name useStore(state state.name); const age useStore(state state.age); return div{name}, {age}岁/div; }7.2 同时订阅多个状态// 方式1分别订阅推荐性能最优 const count useStore(state state.count); const name useStore(state state.name); // 方式2对象解构任何变化都会重渲染 const { count, name } useStore(); // 方式3使用选择器返回对象任何属性变化都会重渲染 const { count, name } useStore(state ({ count: state.count, name: state.name }));7.3 派生状态选择器const useStore create((set) ({ todos: [ { id: 1, text: 学习 Zustand, completed: false }, { id: 2, text: 写代码, completed: true }, { id: 3, text: 休息, completed: false } ] })); // 派生状态只获取未完成的 todos const activeTodos useStore(state state.todos.filter(todo !todo.completed) ); // 派生状态统计信息 const stats useStore(state ({ total: state.todos.length, completed: state.todos.filter(t t.completed).length, active: state.todos.filter(t !t.completed).length })); // 派生状态排序后的列表 const sortedTodos useStore(state [...state.todos].sort((a, b) a.text.localeCompare(b.text)) );7.4 复杂选择器示例// 购物车示例 const useCartStore create((set) ({ items: [ { id: 1, name: 商品A, price: 100, quantity: 2 }, { id: 2, name: 商品B, price: 200, quantity: 1 } ] })); // 计算总价 const totalPrice useCartStore(state state.items.reduce((sum, item) sum item.price * item.quantity, 0) ); // 计算总数量 const totalItems useCartStore(state state.items.reduce((sum, item) sum item.quantity, 0) ); // 查找特定商品 const findItem (id) useCartStore(state state.items.find(item item.id id) );7.5 使用 shallow 进行浅比较import { create } from zustand; import { shallow } from zustand/shallow; const useStore create((set) ({ user: { name: 张三, age: 25 }, settings: { theme: light, language: zh-CN } })); // 不使用 shallow每次都会重渲染对象引用变化 const { user, settings } useStore(state ({ user: state.user, settings: state.settings })); // 使用 shallow只在属性值变化时重渲染 const { user, settings } useStore( state ({ user: state.user, settings: state.settings }), shallow ); // 或者使用数组形式 const [user, settings] useStore( state [state.user, state.settings], shallow );7.6 选择器优化技巧// 1. 提取选择器到组件外部避免每次渲染重新创建 const countSelector (state) state.count; const nameSelector (state) state.name; function MyComponent() { const count useStore(countSelector); const name useStore(nameSelector); // ... } // 2. 使用 useMemo 缓存复杂选择器 import { useMemo } from react; function TodoList({ filter }) { const todos useStore(state state.todos); const filteredTodos useMemo(() { return todos.filter(todo { if (filter active) return !todo.completed; if (filter completed) return todo.completed; return true; }); }, [todos, filter]); return ul{filteredTodos.map(...)}/ul; } // 3. 创建可复用的选择器 Hook function useActiveTodos() { return useStore(state state.todos.filter(todo !todo.completed)); } function useCompletedTodos() { return useStore(state state.todos.filter(todo todo.completed)); }7.7 带参数的选择器// 创建带参数的选择器函数 const selectTodoById (id) (state) state.todos.find(todo todo.id id); function TodoItem({ id }) { // 使用带参数的选择器 const todo useStore(selectTodoById(id)); if (!todo) return null; return div{todo.text}/div; } // 或者使用 useCallback 创建选择器 function TodoItem({ id }) { const todo useStore( useCallback(state state.todos.find(t t.id id), [id]) ); return div{todo?.text}/div; }7.8 完整示例电商页面// stores/productStore.js const useProductStore create((set) ({ products: [ { id: 1, name: 手机, price: 3999, category: 电子 }, { id: 2, name: 耳机, price: 499, category: 电子 }, { id: 3, name: T恤, price: 99, category: 服装 } ], searchTerm: , category: all, setSearchTerm: (term) set({ searchTerm: term }), setCategory: (category) set({ category }) })); // 组件中使用 function ProductList() { const searchTerm useProductStore(state state.searchTerm); const category useProductStore(state state.category); const products useProductStore(state state.products); // 派生状态过滤后的产品 const filteredProducts useMemo(() { return products.filter(product { const matchesSearch product.name.toLowerCase().includes(searchTerm.toLowerCase()); const matchesCategory category all || product.category category; return matchesSearch matchesCategory; }); }, [products, searchTerm, category]); // 统计信息 const stats useProductStore(state ({ total: state.products.length, categories: [...new Set(state.products.map(p p.category))] })); return ( div p共 {filteredProducts.length} 件商品/p {filteredProducts.map(product ( div key{product.id}{product.name} - ¥{product.price}/div ))} /div ); }八、性能对比// 场景store 有多个状态 const useStore create((set) ({ count: 0, name: 张三, age: 25, address: 北京 })); // ❌ 最差任何状态变化都会重渲染 function BadComponent() { const store useStore(); return div{store.count}/div; } // ✅ 好只在 count 变化时重渲染 function GoodComponent() { const count useStore(state state.count); return div{count}/div; } // ✅ 最好选择器提取到外部 const countSelector state state.count; function BestComponent() { const count useStore(countSelector); return div{count}/div; }九、常见陷阱9.1 在渲染中创建新对象// ❌ 每次渲染都创建新对象导致无限重渲染 const { count, name } useStore(state ({ count: state.count, name: state.name })); // ✅ 使用 shallow 进行浅比较 import { shallow } from zustand/shallow; const { count, name } useStore( state ({ count: state.count, name: state.name }), shallow ); // ✅ 或者分别订阅 const count useStore(state state.count); const name useStore(state state.name);9.2 复杂计算放在选择器中// ❌ 每次渲染都执行复杂计算 function BadComponent() { const expensiveData useStore(state state.items.map(item expensiveOperation(item)) ); } // ✅ 使用 useMemo 缓存计算结果 function GoodComponent() { const items useStore(state state.items); const expensiveData useMemo(() items.map(item expensiveOperation(item)), [items] ); }十、练习题创建一个 Store包含用户信息使用选择器分别获取姓名和年龄创建一个 Todo Store使用选择器获取未完成事项的数量创建一个购物车 Store使用选择器计算总价十一、小结要点说明基础选择器useStore(state state.value)派生状态在选择器中计算性能优化使用选择器避免不必要渲染shallow用于对象/数组的浅比较最佳实践选择器提取到组件外部