Reactive UI -- 反应式编程UI框架入门学习(一)
推荐一个反应式编程的MVVM跨平台框架。
反应式编程
反应式编程是一种相对于命令式的编程范式,由函数式的组合声明来构建异步数据流。要理解这个概念,可以简单的借助Excel中的单元格函数。
上图中,A1=B1+C1,无论B1和C1中的数据怎么变化,A1中的值都会自动变化,这其中就蕴含了反应式/响应式编程的思想。
反应式编程对于数据的处理不关心具体的数据值是多少,只要构建出数据的函数式处理,就能并行的异步处理数据流。
Reactive UI
Reactive UI 是一种反应式编程的跨平台MVVM框架,支持Xamarin Forms、Xamarin.iOS、Xamarin.Android、Xamarin.Mac、Tizen、Windows Forms、WPF 和UWP。
本文对比经典的MVVM框架MVVMLight框架来展示ReactiveUI框架的特殊之处。
在MVVMLight中,依赖属性和命令的绑定一般都是放在Xaml中,并且大部分情况下不需要给控件定义Name属性。
<Button Content="{Binding Content}" Command="{Binding OpenFileCommand}"/>
这是属于弱绑定,在Reactive UI框架中也提供这样的弱绑定,但Reactive UI框架官方推荐使用后台强绑定方式。
在强绑定方式中,需要给控件定义他的Name属性。
<Button Name="btnOpenFile"/>
在界面后台的cs文件中使用强绑定方式。
//BtnContent是ViewModel中的属性,btnOpenFile是界面中的控件,并指定控件需要绑定的依赖属性
this.OneWayBind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content);
在Reactive UI框架中,提供了单向绑定和双向绑定两种绑定类型,上述代码中的OneWayBind是属于ViewModel->View的单向绑定,另外还有一个API Bind则是双向绑定。
this.Bind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content);
之所以官方推荐这样的绑定方式,是因为框架中提供了一个管理ViewModel生命周期的API WhenActivated,解决了Xaml弱绑定方式带来的内存泄露的可能性。
在WhenActivated API的函数回调中进行绑定属性和Command,可以同步跟踪View和对应绑定属性的生命周期,避免发生内存泄露。
this.WhenActivated(dispos => {
this.OneWayBind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content).DisposeWith(dispos);
});
WhenActivated 会在View被激活时同步调用注册的回调函数,注意,在OneWayBind后面新增了一个API调用DisposeWith,他可以确保当界面被销毁时,对应的viewModel及其绑定的属性和命令也会被销毁。
类似的,绑定Commond
this.WhenActivated(dispos => {
this.OneWayBind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content).DisposeWith(dispos); this.BindCommand(ViewModel,
viewModel => viewModel.OpenPage,
view => view.openButton)
.DisposeWith(disposableRegistration);
});
这样的强绑定相比于Xaml中的弱绑定,会有以下的优势:
1.提供了ViewModel的生命周期管理,避免内存泄露。
2.控件和后台属性的对应关系更为直观,提高代码的可阅读性。
当然也有一定的缺陷,会增加代码量,并且增加View和ViewModel的耦合性。
定义属性和命令
在MVVMLight中定义一个带通知的属性和Commond:
private string content ;
public string Content
{
get { return content; }
set
{
content = value;
RaisePropertyChanged(() => Content);
}
} private RelayCommand openFileCommand = null;
public RelayCommand OpenFileCommand
{
get { return openFileCommand = openFileCommand ?? new RelayCommand(OpenFile); }
}
在ReactiveUI中也通成功了类似RaisePropertyChanged和RelayCommand功能的API,RaiseAndSetIfChanged和ReactiveCommand。
private string content;
public string Content
{
get { return content; }
set
{
this.RaiseAndSetIfChanged(ref content,value);
}
} private ReactiveCommand<Unit, Unit> openFileCommand;
public ReactiveCommand<Unit, Unit> OpenFileCommand
{
get { return openFileCommand = openFileCommand ?? ReactiveCommand.Create(() => { }); }
}
其中ReactiveCommand的两个Unit,前一个是传入参数,后一个是返回值。ReactiveCommand的定义与MVVMLight大同小异。
但是在ReactiveUI中,还有更简单方便的定义可通知的属性,使用标记[Reactive]。
[Reactive]
public string Content { get; set; }
以上代码等价于
private string content;
public string Content
{
get { return content; }
set
{
this.RaiseAndSetIfChanged(ref content,value);
}
}
动态数据集合
在.Net中,带通知功能的数据集合一般使用ObservableCollection,但是这个类存在一个限制,不支持多线程操作元素,只能在主线程中增加或者删除元素。所以在多线程操作ObservableCollection的时候,一般都需要通过Dispatcher或者线程上下文来推送操作到UI线程。
针对这个问题,ReativeUI框架提供了更优雅的操作方式,SourceList,SourceCache, ObservableCollectionExtended,都是线程安全的集合,需要和ReadOnlyObservableCollection一起搭配使用,用于创建可绑定的线程安全的数据集合。
//这是用于View绑定的数据集合
private readonly ReadOnlyObservableCollection<string> _disks;
public ReadOnlyObservableCollection<string> Disks => _disks; //这里的ObservableCollectionExtended和SourceList作用相同,都是与_disks强关联并创
//建副本集合,在操作数据的时候,不直接操作_disks或者Disks,而是对DisksSource或
//DisksSource2进行操作,会自动的同步到_disk集合并更新到绑定的UI,而Disks用于界面绑定。
public ObservableCollectionExtended<string> DisksSource;
public SourceList<string> DisksSource2;
//以下代码是将DiskSource和DiskSource2与_disk建立强关联关系的两种方式
DisksSource = new();
DisksSource.ToObservableChangeSet()
.Bind(out _disks)
.Subscribe(); DisksSource2 = new SourceList<string>();
DisksSource2.Connect().Bind(out _disks).Subscribe();
函数式组合声明
以一个读取磁盘文件夹信息的小功能为例。
一般都需要定义一个ObservableCollection的Model集合,在子线程中需要通过Dispatcher操作集合。
public ObservableCollection<FolderModel> FolderModels { get; set; }
private async Task LoadFolderInfoWithSelectedDiskChanged(string diskName)
{
await Task.Run(() => { var files = Directory.GetDirectories(diskName);
foreach (var fileName in files)
{
FolderModel folderModel = new FolderModel();
DirectoryInfo directoryInfo = new DirectoryInfo(fileName);
folderModel.FolderName = directoryInfo.Name;
folderModel.CreateTime = directoryInfo.CreationTime;
_dispatcher.Invoke(() => { FolderModels.Add(folderModel);
});
}
});
}
而在ReactiveUI 框架中,不需要Dispatcher这个东西,而是需要通过一个辅助类ObservableAsPropertyHelper。
ObservableAsPropertyHelper 是一个简化 IObservable 和 ViewModel 上的属性之间的互操作的类,为一个普通属性和一个IObservable对象之间建立观察者模式的联系。
以上代码可以修改成:
//当前选中的磁盘符号,是一个IObservable对象
[Reactive]
public string SelectedDisk { get; set; }
//使用ObservableAsPropertyHelper包装普通属性
private readonly ObservableAsPropertyHelper<IEnumerable<FolderModel>> _folderModels;
//FolderModels可用于强绑定
public IEnumerable<FolderModel> FolderModels => _folderModels.Value;
//将_folderModels和SelectedDisk建立观察者和被观察者联系,构建函数组合式声明,当SelectedDisk改变时,
//会自动触发所注册的事件并自动给指定的属性FolderModels赋值。
_folderModels = this.WhenAnyValue(s => s.SelectedDisk)
.Where(s => !string.IsNullOrWhiteSpace(s))
.SelectMany(LoadFolderInfoWithSelectedDiskChanged)
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, nameof(FolderModels));//将计算后得到的结果赋值到指定的属性中 private async Task<IEnumerable<FolderModel>> LoadFolderInfoWithSelectedDiskChanged(string diskName)
{
List<FolderModel> folderModels = new List<FolderModel>();
var files = Directory.GetDirectories(diskName);
foreach (var fileName in files)
{
FolderModel folderModel = new FolderModel();
DirectoryInfo directoryInfo = new DirectoryInfo(fileName);
folderModel.FolderName = directoryInfo.Name;
folderModel.CreateTime = directoryInfo.CreationTime;
folderModels.Add(folderModel);
}
//这个方法中不需要操作FolderModels 只需要把结果返回即可
await Task.CompletedTask;
return folderModels;
}
其中ObservableAsPropertyHelper包装的对象是可以任何对象,而LoadFolderInfoWithSelectedDiskChanged方法必须要带有结果返回的异步方法,这样就构成了函数式声明的异步数据流。
本文列了一些ReactiveUI的简单使用,下一篇会通过一个实例代码进一步学习ReactiveUI框架
Reactive UI -- 反应式编程UI框架入门学习(一)的更多相关文章
- Reactive UI -- 反应式编程UI框架入门学习(二)
前文Reactive UI -- 反应式编程UI框架入门学习(一) 介绍了反应式编程的概念和跨平台ReactiveUI框架的简单应用. 本文通过一个简单的小应用更进一步学习ReactiveUI框架的 ...
- 响应式编程系列(一):什么是响应式编程?reactor入门
响应式编程 系列文章目录 (一)什么是响应式编程?reactor入门 (二)Flux入门学习:流的概念,特性和基本操作 (三)Flux深入学习:流的高级特性和进阶用法 (四)reactor-core响 ...
- (转)Spring Boot 2 (十):Spring Boot 中的响应式编程和 WebFlux 入门
http://www.ityouknow.com/springboot/2019/02/12/spring-boot-webflux.html Spring 5.0 中发布了重量级组件 Webflux ...
- Spring Boot 2 (十):Spring Boot 中的响应式编程和 WebFlux 入门
Spring 5.0 中发布了重量级组件 Webflux,拉起了响应式编程的规模使用序幕. WebFlux 使用的场景是异步非阻塞的,使用 Webflux 作为系统解决方案,在大多数场景下可以提高系统 ...
- 函数响应式编程(FRP)从入门到”放弃”——基础概念篇
前言 研究ReactiveCocoa一段时间了,是时候总结一下学到的一些知识了. 一.函数响应式编程 说道函数响应式编程,就不得不提到函数式编程,它们俩到底有什么关系呢?今天我们就详细的解析一下他们的 ...
- 深度学习初探——符号式编程、框架、TensorFlow
一.命令式编程(imperative)和符号式编程(symblic) 命令式: import numpy as np a = np.ones(10) b = np.ones(10) * 2 c = b ...
- 1小时让你掌握响应式编程,并入门Reactor
我看同步阻塞 “你知道什么是同步阻塞吗”,当然知道了.“那你怎么看它呢”,这个... 在同步阻塞的世界里,代码执行到哪里,数据就跟到哪里.如果数据很慢跟不上来,代码就停在那里等待数据的到来,然后再带着 ...
- EasyUI-EasyUI框架入门学习
前言 新项目的开发前端技术打算采用EasyUI框架(基于EasyUI较为丰富的UI组件库),项目组长将前端EasyUI这块的任务分配给了我.在进行开发之前,需要我这菜鸟对EasyUI框架进行一些基础的 ...
- reactive stream: 响应式编程
既然 Reactive Stream 和 Java 8 引入的 Stream 都叫做流,它们之间有什么关系呢?有一点关系,Java 8 的 Stream 主要关注在流的过滤,映射,合并,而 Reac ...
随机推荐
- Git移除远程已经上传的文件
我们常常会将本地的一些秘钥文件不小心推送到远端,此时仅仅修改本地的.gitignore文件,然后再提交推送是不能将远端的此文件删除的. 此时可以用下面的命令 git rm --cached filen ...
- 工具分享:清理 Markdown 中没有引用的图片
前言: 之前,我写笔记的工具一直都是 notion,而且没有写博客的习惯.但是一是由于 notion 的服务器在国外,有时候很不稳定:二是由于 notion 的分享很不方便,把笔记分享给别人点开链接之 ...
- Linux 中递归删除文件
递归删除当前目录下以 .json 结尾的文件 find . -name "*.json" | xargs rm -f find . -name "*.json" ...
- pandas:多层索引
多层索引是指在行或者列轴上有两个及以上级别的索引,一般表示一个数据的几个分项. 1.创建多层索引 1.1通过分组产生多层索引 1.2由序列创建 1.3由元组创建 1.4可迭代对象的笛卡尔积 1.5将D ...
- 中国程序员容易发错音的单词「GitHub 热点速览 v.22.23」
中国程序员容易发错音的单词,像极了学生时代的纠错本,收录着偶尔会忘记的单词.不过,它似乎更新频率跟不上我们的进步速度,至少一半以上的单词读起来是没有压力的.同样没有压力的还有让应用程序动起来的 aut ...
- 深度学习与CV教程(12) | 目标检测 (两阶段,R-CNN系列)
作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/37 本文地址:http://www.showmeai.tech/article-det ...
- sublime_text 3安装Emmet时出现PyV8警告
使用Emmet是需要在PyV8依赖下才可以的.1. 下面是下载网址:PyV8下载地址 下载自己系统版本的压缩包,然后解压,自己创建一个名为PyV8文件夹.将解压后的文件放入该文件夹里. 打开首选项里的 ...
- NFS网络文件系统搭建
1. 简介 NFS, 就是network file system的简称. 可以通过NFS, 来共享不同主机的文件.目录. 2010年,NFS已经发展到v4.1版本. 2. 应用场景 在中小型企业中,N ...
- UiPath条件判断活动Flow Decision的介绍与使用
一.Flow Decision介绍 FlowDecision节点是一个条件节点,它根据指定条件是否成立来控制流程的两个分支. 当条件为True时,流程执行一个分支 当条件为False时,流程执行另外一 ...
- 【python基础】第08回 流程控制 for循环
本章内容概要 1.循环结构之 for 循环 本章内容详解 1.循环结构之for循环 1.1 语法结构 for 变量名 in 可迭代对象: #字符串 列表 字典 元组 for 循环的循环体代码 针对变量 ...