在Compose中方便的使用MVI思想?试试useReducer!

写在前面

本文中提及的use开头的函数,都出自与我的 ComposeHooks 项目,它提供了一系列 React Hooks 风格的状态封装函数,可以帮你更好的使用 Compose,无需关系复杂的状态管理,专心于业务与UI组件。

这是系列文章的第四篇,前文:

  • 在Compose中使用useRequest轻松管理网络请求
  • 在Compose中使用状态提升?我提升个P…Provider
  • 在Compose中父组件如何调用子组件的函数?

什么是 MVI?

什么是 MVI?想必你也看过很多博客了,其实简单说就是:明确分离数据模型(Model)、用户界面(View)和用户意图(Intent,也称为事件、动作),以实现UI的响应式和可预测的更新

它与 MVVM 其实区别不大,有别于 MVVM 的是,MVVM 将耦合代码按照职责区分,拆分文件。借助 LiveData 或者 DataBinding 将 VM 中数据更新直接驱动 V 层,实现了 V 层与 M 层之间的解耦。

得力于 Compose 带来的状态驱动视图能力,我们可以理解 MVI 思想为:用户发出事件,事件驱动状态变化,状态驱动 UI 变化。这也就是所谓的事件向上,状态向下:事件从组件发出,单一可信来源的状态驱动组件更新。

从这一思想出发,我们可以理解为:谁持有状态,谁就是 M 层。那么过去的 MVVM 的文件拆分将会变得相对松散,我们完全可以摒弃过去那种全屏式思想,不再根据屏幕创建一个 VM 大管家。而是拆分成职责、粒度更细小的组件思想。

当然使用 MVVM 我们一样可以做到类似的效果,但是 MVI 将它流程化、标准化,所以可以理解为其实 MVI 就是一个有一定模板的更优秀的 MVVM

过去我们的 VM 层其实也很重,一个复杂的页面,数个网络接口,都被仍在一个 VM 中,鉴于不同开发者的水平的参差,我们项目中甚至有一个 VM 文件中持有了20多个 LiveData,可以说完全违背了 MVVM 的初衷。

同样的,想必你已经看了很多在 Compose 中使用 ViewModel 来实现 MVI 的文章了吧(我甚至看过回字的四种写法),它真的有这么复杂么?在 Compose 中我们还需要这样的一位大管家么?虽然很多例子、甚至官方的 demo,都还在使用 ViewModel,但是这是一种无法回避的取舍?还是既往路线的惯性。

在一些场景下,我们完全可以更组件化思维,在更小粒度上应用 MVI。今天你可以试试一点新东西:useReducer,通过它我将进一步阐述我所说的:松散的、组件下的 MVI 思想。

我们需要 VM 么?

MVI 相关文章中你可能会看到一个观点:纯函数(给定相同的输入时,总是产生相同的输出,并且不产生任何副作用的函数)。

我们构建一个改变状态的函数,称之为 reducer 函数,将上一个状态、Intent(也成为 event、action)作为函数的入参,将返回值作为新的状态应用于组件,只要这个 reducer 函数是 纯函数,我们就实现了:可预测的更新

现在我们来构建一个最简单的例子:

// 构建状态类型
data class SimpleData(val name: String,val age: Int,
)// Intent、我们一半习惯称之为:action,使用 sealed interface可以方便的实现
sealed interface SimpleAction {data class ChangeName(val newName: String) : SimpleActiondata object AgeIncrease : SimpleAction
}// 构建一个 Reducer 函数,泛型是状态的类型
val simpleReducer: Reducer<SimpleData> = { prevState: SimpleData, action: Any ->when (action) {is SimpleAction.ChangeName -> prevState.copy(name = action.newName)is SimpleAction.AgeIncrease -> prevState.copy(age = prevState.age + 1)else -> prevState}
}

reducer 函数中我们要使用 不可变 数据,data class 就是最好的选择,通过 copy 函数返回新的状态。

这些代码,要么是类型声明、要么是一个纯函数,他们与最终组件息息相关,但是他们并需要放到一个 ViewModel 类中,再想一想我们需要 ViewModel 么?

上面的代码几乎已经是 MVI 的完整实现了,M层状态:SimpleData,I层由Action、Reducer函数构成。

他们非常简单、容易理解,而且可以方便的扩展,规范了M层变化(你不能直接修改状态,必须通过传递 Action 给 Reducer 函数驱动状态变化)。

再来看看我们的 V 层需要做什么?

@Composable
fun UseReducerExample() {val (state, dispatch) = useReducer(simpleReducer, initialState = SimpleData("default", 18))val (input, setInput) = useState("")Surface {Column {Text(text = "UserName: $state.name")Text(text = "UserAge: $state.age")OutlinedTextField(value = input, onValueChange = setInput)TButton(text = "changeName") {dispatch(SimpleAction.ChangeName(input))}TButton(text = "+1") {dispatch(SimpleAction.AgeIncrease)}}}
}

我们只需要使用:useReducer函数,传入 Reducer 函数与一个初始状态:initialState,通过解构声明语法,可以轻松的拿到状态dispatch函数。

然后在组件中使用即可。

我们不一定需要 VM!

现在我可以回答我之前的问题了,我们真的需要么?

很多场景我们其实并不需要,将状态从 VM 中拆解、粒化成为更小的一个个组件,在组件文件中直接声明这些状态类型、Action、Reducer 函数,然后通过 useReducer 函数即可。

在大多数场景也许我们根本就用不上 VM 带给我们的好处,它在 View 体系是那么重要,但是在 Compose 中,我认为有点可有可无了。

比如生命周期感知,除非你要使用旋转屏幕下的状态保持(大多数应用都是锁方向的)?

比如数据共享,了解一下状态提升?了解一下 useContext

如果你是旧的 MVVM 项目改造,那么使用 vm 改造成本比较小,如果你是新项目,我觉得一般的场景完全没有必要继续使用 VM 了。

探索更多

项目开源地址:junerver/ComposeHooks

MavenCentral:hooks

implementation("xyz.junerver.compose:hooks:1.0.8")

欢迎使用、勘误、pr、star。

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

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

相关文章

2024 ccfcsp认证打卡 2023 03 01 田地丈量

import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner in new Scanner(System.in);int n in.nextInt(); // 输入 n&#xff0c;表示矩形的数量int a in.nextInt(); // 输入 a&#xff0c;表示整个区域的长度int b in.nextInt()…

Hive详解(5)

Hive 窗口函数 案例 需求&#xff1a;连续三天登陆的用户数据 步骤&#xff1a; -- 建表 create table logins (username string,log_date string ) row format delimited fields terminated by ; -- 加载数据 load data local inpath /opt/hive_data/login into table log…

如何在Portainer中创建Nginx服务并搭建静态站点实现公网访问本地网站

文章目录 前言1. 安装Portainer1.1 访问Portainer Web界面 2. 使用Portainer创建Nginx容器3. 将Web静态站点实现公网访问4. 配置Web站点公网访问地址4.1公网访问Web站点 5. 固定Web静态站点公网地址6. 固定公网地址访问Web静态站点 前言 Portainer是一个开源的Docker轻量级可视…

Nginx入门 -- 理解Nginx基础概念:连接(Connection)

在Nginx中&#xff0c;连接&#xff08;Connection&#xff09;是一个基础而又关键的概念。它涉及到服务器与客户端之间的通信、并发处理以及网络性能优化等方面。本文将深入探讨Nginx中连接的概念、类型以及相关的优化策略&#xff0c;帮助读者更好地理解和利用Nginx来构建高性…

报错:TypeError: Cannot handle this data type: (1, 1, 3), <f8

报错内容&#xff1a; 解决方法&#xff1a; 这个错误是由于 PIL 库无法处理特定的数据类型引起的。为了解决这个问题&#xff0c;你可以尝试将数据类型转换为 PIL 可以处理的类型&#xff0c;比如转换为 uint8 类型。你可以在调用 Image.fromarray() 方法之前&#xff0c;将…

SQL,group by分组后分别计算组内不同值的数量

SQL&#xff0c;group by分组后分别计算组内不同值的数量 如现有一张购物表shopping 先要求小明和小红分别买了多少笔和多少橡皮&#xff0c;形成以下格式 SELECT name,COUNT(*) FROM shopping GROUP BY name;SELECT name AS 姓名,SUM( CASE WHEN cargo 笔 THEN 1 ELSE 0 END)…

Java中copy 一个list,不用BeanUtils.copyProperties

1.List不起作用&#xff08;单个对象拷贝有用&#xff0c;list没有用&#xff09; cn.hutool.core.bean.BeanUtils.copyProperties(a, b); org.springframework.beans.BeanUtils.copyProperties(a, b); 2.有效&#xff08;使用JSONObject 先转成字符串再转成List对象&#x…

Java基础知识总结(31)

函数式接口 所谓函数式接口&#xff0c;就是接口中有且只能有一个抽象方法。用FunctionalInterface注解标注&#xff0c;接口可以包含多个默认方法、类方法&#xff0c;私有方法。 方法引用 如果Lambda 表达式的代码块只有一条代码&#xff0c;还可以在代码块中使用方法引用…

Qt QML 坐标转换函数

QML坐标转换 版本相关函数 版本 Qt5.15 相关函数 下面这些函数是隶属于Item的 /// Item object mapFromGlobal(real x, real y) object mapFromItem(Item item, rect r) object mapFromItem(Item item, real x, real y, real width, real height) object mapFromItem(Item …

安装使用Scoop

目标 今天看到一款用于windows系统的包管理工具Scoop&#xff0c;可以在powershell的命令行里安装软件&#xff0c;省去了找软件、配置环境等步骤&#xff0c;看起来还是很香的&#xff0c;正好最近再做csapp的家庭作业&#xff0c;安装个gcc看看是不是这么牛 安装Scoop 按照…

使用CRXjs、Vite、Vue 开发 Chrome 多页面插件,手动配置 vite.config.ts 和 manifest.json 文件

一、使用CRXjs、Vite、Vue 开发 Chrome 多页面插件&#xff0c;手动配置 vite.config.ts 和 manifest.json 文件 一、创建 Vue 项目 1. 使用 Vite 创建 Vue 项目 npm create vitelatest # npm yarn create vite # yarn pnpm create vite # pnpm选择 Vue 和 TS 进入项目…

在Windows中使用NVM安装node.js

NVM介绍 Node.js版本管理器&#xff08;Node Version Manager&#xff09;&#xff0c;简称NVM&#xff0c;是一款用于在单个系统上轻松安装和管理多个Node.js版本的命令行工具。它允许用户根据项目需求在不同版本之间自由切换&#xff0c;解决了因为不同项目依赖于不同Node.j…

Golang与Java:两种编程语言的对比

Golang与Java&#xff1a;两种编程语言的对比 在编程世界中&#xff0c;Golang和Java是两种非常流行的编程语言&#xff0c;它们各自具有独特的优势和特点。本文将对这两种语言进行详细对比&#xff0c;帮助开发者了解它们之间的差异&#xff0c;以便根据项目需求选择合适的编…

Python快速入门系列-6(Python高级特性)

第六章: Python高级特性 6.1 列表推导式与生成器6.1.1 列表推导式6.1.2 生成器6.1.2.1 生成器表达式6.1.2.2 生成器函数6.2 装饰器与迭代器6.2.1 装饰器6.2.2 迭代器6.3 异常处理与错误调试6.3.1 异常处理6.3.1.1 try-except语句6.3.1.2 try-except-else语句6.3.2 错误调试6.3…

【缺陷】硅光电二极管中的DT侧壁陷阱态的DLTS表征

【A DLTS study on Deep Trench Processing induced Trap States in Silicon Photodiodes】 概括 本研究通过深能级瞬态光谱&#xff08;DLTS&#xff09;技术对硅光电二极管中的深沟槽&#xff08;DT&#xff09;侧壁诱导的陷阱态进行了详细分析。研究发现&#xff0c;这些陷…

golang语言系列:通用技能之 Scrum、Kanban等敏捷管理策略

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 golang语言系列 文章&#xff0c;主要对编程通用技能 Scrum、Kanban等敏捷管理策略 进行学习 1.什么是敏捷开发 敏捷是一个描述软件开发方法的术语&#xff0c;它强调增量交付、团队协作、持续规划和持续学习。…

深入探秘Python生成器:揭开神秘的面纱

一、问题起源&#xff1a; 想象一下&#xff0c;您掌握了一种魔法&#xff0c;在代码世界里&#xff0c;您可以轻松呼唤出一个整数。然而&#xff0c;事情并不总是看起来那样简单。在Python的奇妙王国中&#xff0c;我遇到了一个有趣的谜题&#xff1a; def tst():try:print(…

电商新秀视频号小店,2024年值得加入吗?

大家好&#xff0c;我是电商糖果 视频号小店去年的热度非常高&#xff0c;很多第一批入驻的商家&#xff0c;也赚的盆满钵满。 于是就有不少商家问糖果&#xff0c;关于视频号小店2024值不值得加入&#xff0c;想听听我的看法。 糖果做电商有六七年的时间了&#xff0c;喜欢…

2024.2.9力扣每日一题——二叉树的最近公共祖先

2024.2.9 题目来源我的题解方法一 后序遍历方法二 存储父节点&#xff08;哈希表List&#xff09; 题目来源 力扣每日一题&#xff1b;题序&#xff1a;236 我的题解 方法一 后序遍历 后序遍历可以携带一些子节点信息&#xff0c;通过后序遍历可以知道节点p和节点q在根节点的…

基于springboot实现网上点餐系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现网上点餐系统演示 摘要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于网上点餐系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了网上点餐系统…