ViewModel负责组装界面状态State。引发State变换的原因有很多,比如用户点击某个按钮,一次网络请求受到应答,一次本地数据库查询返回结果等等。因此ViewModel是根据各种事件生成State的对象,换句话说,是一个从多个事件流到状态的映射。

ViewModel: (InputFlow, NetworkFlow, DatabaseFlow, ...) -> State

用图来表示就是

                         ┌─────────┐
用户交互 ───▶ 事件流 ───┤         ││ViewModel├───▶ UI 状态 ───▶ 渲染UI
数据变更 ───▶ 数据流 ───┤         │└─────────┘

1. 常见事件流处理模式

我们考虑处理两个事件流的模式。多事件流的处理方法是类似的。

1.1. 模式1:取各自最新值

combine(flowA, flowB) { a, b ->// 当某个流更新时,获取两个流各自的最新值。Result(a, b)
}

适用场景:表单联动验证(如邮箱+密码验证)、实时数据看板。

1.2. 模式2:合并流

merge(flowA, flowB) // 任意流更新时触发,取两个流中的最新值。

适用场景:同类型事件合并(如多个按钮点击事件)

1.3. 模式3:流水线

flowA.flatMapLatest { aValue -> flowB.map { bValue -> Processed(aValue, bValue) }
}

适用场景:搜索建议(关键词变化触发新查询)、参数化数据加载

1.4. 模式4:序号匹配

flowA.zip(flowB) { a, b -> // 严格按序号匹配,双方各发1次才触发。Pair(a, b) 
}

适用场景:分页加载、操作-响应确认机制。

1.5. 模式5:优先流

combine(flowA, flowB) { a, b ->when {a.priority > b.priority -> Result(a)b.isCritical -> Result(b)else -> Result.merge(a, b)}
}

特点:实现业务规则主导的数据优先级。 适用场景:多源数据冲突处理、关键操作优先。

1.6. 模式6:将异常转换为事件

val flowAState = flowA.map { Success(it) }.catch { emit(Error(it)) }.stateIn(viewModelScope, SharingStarted.Lazily, Loading)

特点:单个流的失败不影响整体功能。 适用场景:模块化数据展示、独立可失败操作。

2. 常见问题和方案

2.1. 问题1:输入流过多

输入流过多会导致代码可读性下降。封装中间事件和中间流可以解决这个问题。

// 创建中间组合流
val userPreferences = combine(themeFlow, fontSizeFlow, layoutFlow) { UserPrefs(it[0], it[1], it[2]) 
}val finalState = combine(userPrefs, contentFlow) { ... }

2.2. 问题2:状态频繁更新导致UI抖动

在流中增加防抖可以避免UI抖动。

searchQueryFlow.debounce(300)          // 防抖.distinctUntilChanged().flatMapLatest { query -> repository.search(query).map { it.toState() } }

2.3. 问题3:订阅泄漏

使用stateIn和flatMapLatest可以避免订阅泄露。

代码1  错误例子:

init {viewModelScope.launch {   // 直接启动协程dataFlow.collect { ... }}
}

代码2  正确例子

val state = dataFlow.map { ... }.stateIn(                 // 使用stateIn扩展scope = viewModelScope,started = SharingStarted.Eagerly,initialValue = ...)

2.4. 问题4:错误恢复

使用retryWhen可以从错误中进行恢复。

flowA.retryWhen { cause, attempt -> if (attempt < 3) delay(200 * attempt) else throw cause}.catch { emit(fallbackData) }

2.5. 问题5:动态切换流

val activeFlow = MutableStateFlow<Flow<Data>>(flowA)val result = activeFlow.flatMapLatest { it }

支持在运行时切换数据源(如测试模式/生产模式切换)。

2.6. 问题6:缓存共享流

复用单数据源,减少重复订阅。

// 创建共享流
val sharedFlow = repository.dataSource.shareIn(viewModelScope, SharingStarted.WhileSubscribed())// 分流处理
val stateA = sharedFlow.map { extractA(it) }
val stateB = sharedFlow.map { extractB(it) }

2.7. 问题7:将业务状态作为界面状态

不要直接使用业务状态作为界面状态(业务状态极为简单的除外)。

// 错误方式:混合业务状态和UI控制状态
data class BadState(val data: List<Item>,val loading: Boolean,val toastMessage: String?, // UI控制状态val dialogVisible: Boolean // UI控制状态
)// 正确方式:分层状态
// 业务状态
data class BusinessState(val data: List<Item>, val error: Throwable?)// UI状态(由业务状态转换)
val uiState = businessState.map { when {it.data.isEmpty() -> EmptyStateit.error != null -> ErrorState(it.error)else -> SuccessState(it.data)}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87172.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87172.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/87172.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

javaweb Day2

PreparedStatement作用: 预编译SQL语句并执行: 预防SQL注入问题 SQL注入:SQL注入是通过操作输入来修改事先定义好的SQL语句&#xff0c;用以达到执行代码对服务器进行攻击的方法。

Java项目:基于SSM框架实现的中学教学管理系统【ssm+B/S架构+源码+数据库+毕业论文+开题报告】

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本景海中学教学管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据…

JVM调优实战 Day 15:云原生环境下的JVM配置

【JVM调优实战 Day 15】云原生环境下的JVM配置 文章标签 jvm调优, 云原生, Java性能优化, JVM参数配置, 容器化部署, Kubernetes, Docker, JVM在云原生中的应用 文章简述 随着云原生技术的普及&#xff0c;Java 应用越来越多地运行在容器&#xff08;如 Docker&#xff09;和…

数据结构day7——文件IO

一、标准 IO 的起源与概念 标准 IO&#xff08;Standard Input/Output&#xff09;是由 Dennis Ritchie 在 1975 年设计的一套 IO 库&#xff0c;后来成为 C 语言的标准组成部分&#xff0c;并被 ANSI C 所采纳。它是对底层文件 IO 的封装&#xff0c;提供了更便捷、可移植的文…

6.Docker部署ES+kibana

部署ES&#xff08;Elasticsearch&#xff09;kibana 1.ES暴露的端口很多 2.ES十分消耗内存 3.ES的数据一般需要挂载出去&#xff0c;放在安全目录&#xff08;挂载) elastic 前往官方手册 1.下载运行elasticsearch的 docker run -d --name elasticsearch --net somenet…

使用mysqldump对mysql数据库进行备份

目录 1软件说明 2语法格式 3备份流程 3.1只备份指定数据库中表和数据 3.1.1准备目录 3.1.2备份db1数据库里面的所有表信息 3.1.3还原备份 3.2备份数据库结构 3.2.1备份db1数据库的结构和数据 3.2.2还原数据库 3.3备份所有数据库 3.3.1备份数据库 3.3.2还原数据库 1…

vue3路由跳转打开新页面

Vue3 路由跳转打开新页面的方法 在 Vue3 中&#xff0c;有几种方法可以实现路由跳转时打开新页面&#xff1a; 1. 使用 router.resolve 方法 import { useRouter } from vue-routerconst router useRouter()const openNewPage (path) > {const resolved router.resolv…

SeaTunnel 社区 2 项目中选“开源之夏 2025”,探索高阶数据集成能力!

Apache SeaTunnel 社区在“开源之夏 2025”中再传捷报&#xff0c;共有两个项目成功入选&#xff0c;聚焦于 Flink CDC schema 支持与元数据管理的生态扩展方向&#xff0c;体现出 SeaTunnel 在实时数据集成和平台化能力构建上的深入布局。 中选项目与学生如下&#xff1a; 《…

Neo4j无法建立到 localhost:7474 服务器的连接出现404错误

一、确认中文路径问题&#xff08;核心原因&#xff09; 安装路径包含中文&#xff0c;可能导致 Windows 命令行或 Neo4j 解析路径时出错。 解决方法&#xff1a; 重新安装 Neo4j 到英文路径&#xff08;推荐&#xff09;&#xff1a; 将 Neo4j 解压或安装到不含中文的目录&a…

锂离子电池均衡拓扑综述

锂离子电池均衡拓扑综述 一、引言 锂离子电池因其高能量密度、长循环寿命等优点&#xff0c;在电动汽车、储能系统等领域得到了广泛应用。然而&#xff0c;电池组在使用过程中&#xff0c;由于电池个体差异、充放电管理等因素&#xff0c;会出现荷电状态&#xff08;SOC&…

[面试] 手写题-浅拷贝,深拷贝

浅拷贝 // 浅拷贝 function shallow(obj) {const newObj {}for (const key in obj) {// 保证 key 不是原型的属性if (obj.hasOwnProperty(key)) {newObj[key] obj[key]}}return newObj }深拷贝 递归 O(n^2) // 深拷贝 function deepClone(obj {}) {// 如果传入的是 null&am…

BehaviorTree.ROS2安装记录

坑比库&#xff0c; 首先 git clone https://github.com/BehaviorTree/BehaviorTree.ROS2.git 依赖 git clone https://github.com/PickNikRobotics/cpp_polyfills.git git clone https://github.com/PickNikRobotics/RSL.git git clone https://github.com/PickNikRobotics/…

Vue基础(19)_Vue内置指令

我们学过的vue内置指令&#xff1a; v-bind&#xff1a;单向绑定解析表达式&#xff0c;可简写为&#xff1a;:xxx v-model&#xff1a;双向数据绑定 v-for&#xff1a;遍历数组/对象/字符串 v-on&#xff1a;绑定事件监听&#xff0c;可简写为 v-if&#xff1a;条件渲染(动态控…

排列组合初步

什么是排列组合 排列组合是计数问题&#xff0c;顺序不同且值相同算两种方案是排列&#xff0c;顺序不同且值相同算一种方案是组合。 暴力枚举方案能算出方案数&#xff0c;太耗时&#xff0c;运用加法原理和乘法原理可降低时间复杂度。先将原问题拆解成子问题&#xff0c;根…

SQL调优方案对比与最佳实践

问题背景介绍 在大型互联网或企业级应用中&#xff0c;数据库往往成为系统性能的瓶颈。随着数据量和并发量的增长&#xff0c;单一的 SQL 查询可能出现响应迟缓、锁等待、全表扫描等性能问题。为保证系统的稳定性和用户体验&#xff0c;需要对 SQL 查询做深入的调优。常见的调…

Terraform Helm:微服务基础设施即代码

&#x1f680; Terraform & Helm&#xff1a;微服务基础设施即代码 &#x1f4da; 目录 &#x1f680; Terraform & Helm&#xff1a;微服务基础设施即代码1. 引言 &#x1f680;2. 环境与依赖 &#x1f9f0;3. 架构示意 &#x1f3d7;️4. Terraform 定义云资源 &…

清理 Docker 缓存占用

Docker 缓存主要包括未使用的镜像、容器、卷和网络等资源。清理缓存可以提高磁盘空间&#xff0c;线上升级次数比较多的话&#xff0c;服务器中Docker缓存会非常严重&#xff0c;做下清理瘦身会有意想不到的效果 清理未使用的镜像 运行以下命令删除未被任何容器引用的镜像&…

深入解析NumPy的核心函数np.array()

深入解析NumPy的核心函数np.array NumPy与np.array()简介NumPy的重要性np.array()的作用 np.array()函数的详细参数object参数dtype参数copy参数order参数subok参数ndmin参数like参数 np.array()函数的使用示例创建基本的一维和二维数组创建具有特定数据类型的数组创建多维数组…

定时器的设计

定时器 定时器原理如何理解定时器定时器数据结构选取定时器触发方式 定时器的实现 定时器原理 如何理解定时器 定时器在日常通常被描述为组织大量延时任务的模块&#xff0c;其实从字面意思去理解的话&#xff0c;他就是去处理延时任务的&#xff0c;那么什么是延时任务呢&am…

大模型-分布式论文一瞥

1分离式架构 1.1 DistServe DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving 讲的是一个将prefill和decoding分…