Umi-OCR文字识别终极教程:从零开始掌握免费离线OCR工具
2026/6/16 12:49:56
| 维度 | 内容 |
|---|---|
| What | 使用 useStore 和选择器读取状态 |
| Why | 优化性能,只在相关状态变化时重渲染 |
| When | 组件需要订阅 store 中的部分状态 |
| Where | 组件内部 |
| Who | 使用 Zustand 的开发者 |
| How | const count = useStore(state => state.count) |
选择器(Selector)是一个函数,从 store 中提取特定的状态片段。Zustand 使用选择器来优化组件渲染性能。
// 选择器函数:从完整状态中提取需要的部分 const countSelector = (state) => state.count; const userSelector = (state) => state.user; // 使用选择器 const count = useStore(countSelector);不使用选择器时,组件会在任何状态变化时重渲染:
// ❌ 任何状态变化都会导致组件重渲染 const { count, name, age } = useStore(); // ✅ 只在 count 变化时重渲染 const count = useStore(state => state.count);选择器让你只订阅组件真正需要的状态,避免不必要的渲染。
| 场景 | 推荐方式 | 说明 |
|---|---|---|
| 订阅单个状态 | 使用选择器 | state => state.count |
| 订阅多个状态 | 使用选择器返回对象 | state => ({ count: state.count, name: state.name }) |
| 派生状态 | 在选择器中计算 | state => state.todos.filter(t => !t.completed) |
| 整个 store | 不使用选择器 | 性能较差,不推荐 |
// 组件内 function MyComponent() { const count = useStore(state => state.count); } // 自定义 Hook function useCount() { return useStore(state => state.count); }所有使用 Zustand 的开发者都应该掌握选择器的使用。
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>; }// 方式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 }));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)) );// 购物车示例 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) );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 );// 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)); }// 创建带参数的选择器函数 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>; }// 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>; }// ❌ 每次渲染都创建新对象,导致无限重渲染 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);// ❌ 每次渲染都执行复杂计算 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] ); }| 要点 | 说明 |
|---|---|
| 基础选择器 | useStore(state => state.value) |
| 派生状态 | 在选择器中计算 |
| 性能优化 | 使用选择器避免不必要渲染 |
| shallow | 用于对象/数组的浅比较 |
| 最佳实践 | 选择器提取到组件外部 |