在 Zustand 中实现 computed 的方式
注:本文结合本人真实项目实践经验,经过 AI 润色。
引言
在状态管理领域,计算属性(computed properties)是一个极其重要的概念。MobX 和 Pinia 等库都内置了计算属性功能,允许开发者声明式地定义派生状态。虽然 Zustand 本身没有直接提供 computed API,但这并不意味着我们无法实现类似的功能。
本文将介绍三种在 Zustand 中实现计算属性的优雅方式,包含官方推荐等方案。
方案一:derive-zustand
https://github.com/zustandjs/derive-zustand 是 Zustand 官方维护的派生状态库,它提供了一种响应式的方式来处理计算逻辑。
核心优势
- 响应式更新:自动追踪依赖,当依赖状态变化时自动更新
- 类型安全:完美支持 TypeScript 类型推断
- 性能优化:避免不必要的重新计算
基本用法
import { create, useStore } from 'zustand';
import { derive } from 'derive-zustand';
// 基础 store
const useCountStore = create<{ count: number; inc: () => void }>((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
}));
// 派生 store
const doubleCountStore = derive<number>((get) => get(useCountStore).count * 2);
// 自定义 hook
const useDoubleCountStore = () => useStore(doubleCountStore);
// 组件中使用
const Counter = () => {
const { count, inc } = useCountStore();
const doubleCount = useDoubleCountStore();
return (
<div>
<div>count: {count}</div>
<div>doubleCount: {doubleCount}</div>
<button type="button" onClick={inc}>
+1
</button>
</div>
);
};
适用场景
- 需要多个组件共享的派生状态
- 复杂的计算逻辑需要复用
- 希望保持响应式更新的特性
方案二:手动维护计算属性
对于简单的计算需求,可以直接在 store 中声明派生状态,并在相关操作后手动更新。
实现模式
import { create } from 'zustand';
type CartItem = { id: string; price: number; quantity: number };
type CartState = {
items: CartItem[];
total: number; // ← 计算属性
addItem: (item: Omit<CartItem, 'id'>) => void;
updateTotal: () => void;
};
const useCartStore = create<CartState>((set, get) => ({
items: [],
total: 0,
addItem: (item) => {
set((state) => ({
items: [...state.items, { ...item, id: crypto.randomUUID() }],
}));
get().updateTotal(); // 添加商品后更新总价
},
updateTotal: () => {
const newTotal = get().items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
set({ total: newTotal });
},
}));
优缺点分析
优点:
- 不需要额外依赖
- 逻辑集中,便于维护
- 更新时机明确可控
缺点:
- 需要手动触发更新
- 可能遗漏更新点
- 不适合复杂依赖关系
最佳实践
- 为计算属性添加专门的更新方法
- 在文档中明确标注哪些操作会影响计算属性
- 考虑使用
immer简化不可变更新逻辑
方案三:在组件内派生状态
对于简单的、仅限单个组件使用的派生状态,可以直接在组件内部计算。
实现示例
const UserProfile = () => {
const firstName = useUserStore((s) => s.firstName);
const lastName = useUserStore((s) => s.lastName);
const fullName = `${firstName} ${lastName}`;
return (
<div>{fullName}</div>
);
};
适用条件
- 派生状态只在一个组件中使用
- 计算逻辑非常简单
- 不需要响应式更新(或可以接受组件重新渲染)
性能考虑
当派生计算较复杂时,可以使用 useMemo 优化:
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
方案对比与选择指南
| 方案 | 适用场景 | 复杂度 | 性能 | 维护性 |
|---|---|---|---|---|
| derive-zustand | 多组件共享的复杂派生状态 | 中 | 优 | 优 |
| Store 内维护 | 简单的全局计算属性 | 低 | 良 | 中 |
| 组件内计算 | 单一组件使用的简单派生 | 最低 | 视情况 | 视情况 |
选择建议:
- 优先考虑 derive-zustand,特别是需要响应式更新时
- 对于简单场景,Store 内维护更轻量
- 组件内计算适合临时性、局部性的简单逻辑
高级技巧:组合使用多种方案
在实际项目中,你可以灵活组合这些方案。例如:
// 使用 derive-zustand 创建基础派生状态
const filteredTodosStore = derive<Todo[]>(get => {
const { todos, filter } = get(useTodoStore);
return todos.filter(todo =>
filter === 'all' ||
(filter === 'completed' && todo.completed) ||
(filter === 'active' && !todo.completed)
);
});
// 在组件内进一步派生
const TodoStats = () => {
const filteredTodos = useStore(filteredTodosStore);
// 组件特有的派生状态
const completionPercentage = useMemo(() => {
if (filteredTodos.length === 0) return 0;
const completed = filteredTodos.filter(t => t.completed).length;
return Math.round((completed / filteredTodos.length) * 100);
}, [filteredTodos]);
return <div>完成度: {completionPercentage}%</div>;
};
总结与最佳实践
在 Zustand 中实现计算属性有多种方式,没有绝对的"最佳"方案,关键是根据具体场景选择最合适的:
- 保持简单:不要过度设计,简单的组件内计算可能就足够了
- 关注性能:对于昂贵的计算,使用 memoization 技术
- 类型安全:充分利用 TypeScript 确保类型正确
- 文档说明:明确标注哪些是计算属性及其依赖关系
- 测试覆盖:为重要的计算逻辑添加单元测试
Zustand 的灵活性允许你根据项目需求选择最适合的计算属性实现方式,这种设计哲学正是它受到开发者喜爱的原因之一。
在 Zustand 中实现 computed 的方式的更多相关文章
- Vuejs中关于computed、methods、watch的区别。
Vue.js在模板表达式中限制了,绑定表达式最多只能有一条表达式,但某些数据需要一条以上的表达式运算实现,此时就可以将此数据放在计算属性(computed)当中. Vuejs中关于computed.m ...
- 详解Vue中的computed和watch
作者:小土豆 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.cn/user/2436173500265335 1. 前言 作为一名Vue ...
- opengl es中不同的绘制方式
opengl es中不同的绘制方式 转载请保留出处:http://xiaxveliang.blog.163.com/blog/static/297080342013467344263/ 1. GL_P ...
- 程序中保存状态的方式之Cookies
程序中保存状态的方式之 Cookies,之前写过一篇关于ViewState的.现在继续总结Cookies方式的 新建的测试页面login <%@ Page Language="C#&q ...
- iOS中的数据持久化方式
iOS中的数据持久化方式,基本上有以下四种:属性列表.对象归档.SQLite3和Core Data. 1.属性列表 涉及到的主要类:NSUserDefaults,一般 [NSUserDefaults ...
- VS中附加进程的方式调试IIS页面,以及设置断点无效问题解决
以前调试网站的时候都习惯是直接在解决方案中右键调试——启动新实例,后来发现这样的缺点有: 1.启动比较慢: 2.一些浏览器的request参数无法带入: 3.不特殊指定启动url的话,VS会将页面加载 ...
- WCF中常用的binding方式
WCF中常用的binding方式: BasicHttpBinding: 用于把 WCF 服务当作 ASMX Web 服务.用于兼容旧的Web ASMX 服务.WSHttpBinding: 比 Basi ...
- 程序中保存状态的方式之ViewState
程序中保存状态的方式有以下几种: 1.Application 2.Cookie 3.Session 4.ViewState:ViewState是保存状态的方式之一,ViewState实际就是一个Hid ...
- Azure Service Bus 中的身份验证方式 Shared Access Signature
var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...
- asp.net开发中常见公共捕获异常方式总结(附源码)
本文实例总结了asp.net开发中常见公共捕获异常方式.分享给大家供大家参考,具体如下: 前言:在实际开发过程中,对于一个应用系统来说,应该有自己的一套成熟的异常处理框架,这样当异常发生时,也能得到统 ...
随机推荐
- Vmware workstation安装部署微软WSUS服务应用系统
简介 WSUS全称Windows Server Update Services,是微软开发的免费服务器角色,用于在企业内网中集中管理Windows系统及微软产品的更新分发.其前身为Windows ...
- C# 在Excel中设置文本的对齐方式、换行、旋转
在 Excel 中,对齐.换行和旋转是用于设置单元格内容显示方式的功能.合理的设置这些文本选项可以帮助用户更好地组织和展示 Excel 表格中的数据,使表格更加清晰.易读,提高数据的可视化效果.本文将 ...
- MVVM - Model和ViewModel的创建和配置
MVVM-Model和ViewModel的创建和配置 本文同时为b站WPF课程的笔记,相关示例代码 简介 MVVM:Model-View-ViewModel,是一种软件架构的模式.通过引入一个中间层V ...
- ElasticSearch高可用部署
简单说明 我们在部署ElasticSearch高可用集群时,要规划好集群的规模,每个节点的职责,规划好后续的水平扩展方案,再进行部署. 核心概念 Cluster:集群,由一个或多个 Elasticse ...
- 在工具类静态方法调用@Autowired注入的bean方法
今天在搞一个工具类的时候,需要在工具类的静态方法中调用mapper的方法插入数据,但是,用spring的@Autowired注入bean后,测试一跑,报空指针异常. 解决方案如下: 1.对工具类使用@ ...
- UI上将BP附件放到BP结果中
1,取附件内容放到新增的字段里 METHOD get_attachment. DATA: current TYPE REF TO if_bol_bo_property_access. DATA: dr ...
- Java 通用对象数值比较方法
前言 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i. 提示:以下是本篇文章正文内容,下面案例可供参考 说明 主要用于比较两个对象是否相等,特别处理了数值类型的比较(包括字符串形式的数值) ...
- Django实战:自定义中间件实现全链路操作日志记录
一.中间件 介绍 在 Django 中,中间件(Middleware)是一组轻量级.底层的插件系统,用于全局地改变 Django 的输入和输出.中间件可以在请求被处理之前和响应返回之前执行代码,从而实 ...
- 你了解 Java 的类加载器吗?类加载机制是什么?什么是双亲委派机制?
什么是类加载器,类加载器有哪些? 实现通过类的全限定名获取该类的二进制字节流的代码块叫做类加载器. 主要有一下四种类加载器: 启动类加载器:用来加载 Java 核心类库,无法被 Java 程序直接引用 ...
- 645仪表以JSON格式上发方法
1.概述 之前我们已经介绍了Modbus RTU仪表实现JSON格式上发云服务器的方法,类似的现在也可以支持645协议的仪表通过JSON格式上发服务器. 卓岚实现645仪表转JSON网关的特点有: 1 ...