Reactive UI -- 反应式编程UI框架入门学习(一)
创始人
2024-02-21 14:24:40
0

反应式编程

反应式编程是一种相对于命令式的编程范式,由函数式的组合声明来构建异步数据流。要理解这个概念,可以简单的借助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属性。

1

这是属于弱绑定,在Reactive UI框架中也提供这样的弱绑定,但Reactive UI框架官方推荐使用后台强绑定方式。

在强绑定方式中,需要给控件定义他的Name属性。

1

在界面后台的cs文件中使用强绑定方式。

1

2

//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 openFileCommand;public ReactiveCommand 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一起搭配使用,用于创建可绑定的线程安全的数据集合。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

//这是用于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 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> _folderModels;
//FolderModels可用于强绑定
public IEnumerable 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> LoadFolderInfoWithSelectedDiskChanged(string diskName){List folderModels = new List();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框架

相关内容

热门资讯

石材大板采购合同范本优选55... 石材大板采购合同范本 第一篇购销合同主要是指供方(卖方)同需方(买方)根据协商一致的意见,由供方将一...
个人租车协议合同范本最新14... 个人租车协议合同范本最新 第一篇甲方:乙方:根据《_合同法》及国家、省、市、县有关客运出租汽车管理规...
【数据库提权】MySQL UD... 文章目录前言一、UDF简介二、UDF提权条件三、上传动态链接库文件四、UDF提权步骤MSF漏洞验证五...
极智AI | 百度推出文心一言... 欢迎关注我,获取我的更多经验分享,极智传送《极智AI | 百度推出文心一言,对标 ChatGPT 功...
开宾馆租房合同范本精选24篇 开宾馆租房合同范本 第一篇合同编号:合同各方:出租方(甲方):__________承租方(乙方):根...
采矿设备采购合同范本精选75... 采矿设备采购合同范本 第一篇甲方:乙方:振兴经济,服务社会。经甲、乙双方协商,就零售房屋租赁事宜达成...
keepalived 配置高可... 一、keepalived原理原理:大致是接受对VIP的ARP请求时,将M...
餐饮招聘合同范本推荐29篇 餐饮招聘合同范本 第一篇甲方: 乙方:第一条合同期限:________年____月____日至___...
广州市职工标准劳动合同+简洁... 广州市职工标准劳动合同范本+简洁版  编号:_________  用人单位(甲方)_________...
446061-19-4,DOT... DOTA-p-苯-氨基-四叔丁酯基本信息DOTA-p-苯-氨基-四叔丁酯,又称S-2-...
信息时代的必修课:冗余度(善用... 文章目录 引言I 冗余度1.1 冗余度的定义1.2 冗余度的好处1.3 信息冗余的问题1.4 善用信...
从头开始完成一个STM32例程 创建新项目 Project-> New,之后选择自己的开发板芯片 确定之后又跳到运行...
同事之间合作合同范本优选10... 同事之间合作合同范本 第一篇协议双方甲方:法人代表:《企业法人营业执照》注册号:乙方:法人代表:《企...
艺术作品授权合同范本(优选1... 艺术作品授权合同范本 第一篇作品名称:本人同意委托 作为办理该合作作品(以下简称本作品)出版事宜的代...
使用Spring Boot和C... 原理 Spring Boot是一个基于Spring框架的快速开发应用程序的框架,其提供...
水利合同范本优选14篇 水利合同范本 第一篇发包方:承包方:拟承包魏岗村下周组下周大塘清淤工程,接受了魏岗村下周组的投标,双...
宁德大型仓库租赁合同范本(通... 宁德大型仓库租赁合同范本 第一篇甲方:乙方:第一条:场地用途第二条:期限自年月日起至年月日止。第三条...
超越想象,博睿数据3D数字展厅... 历经多月精心打磨 博睿数据3D数字展厅正式上线 带来一个有温度、易操作、更全面的 线上形象展览平台 ...
承包小区保洁工程合同范本(通... 承包小区保洁工程合同范本 第一篇甲方:___________________乙方:_________...
垫资合同范本 垫资合同范本  现今社会公众的法律意识不断增强,合同的使用频率呈上升趋势,签订合同是减少和防止发生争...